Refactoring of ChatClient to add fluent API and introduce Model as dependent object
* Rename the ModelClient class hierarchy into Model: - Rename ModelClient into Model. Update all code and doc references. - Rename ChatClient to ChatModel. Update all ChatClient suffixes and chatClient fields and variables in code and doc. - Rename EmbeddingClient into EmbeddingModel. Update the XxxEmbeddingClient class and variable suffixes and embeddingClient variables and fields in code and docs. - Rename ImageClient into ImageModel. - Rename SpeechClient into SpeechModel. - Rename TranscriptionClient into TranscriptionModel. - Update all javadocs and antora pages. Update the related diagrams. * Create fluent API in ChatClient interface that now includes streaming support * Add OpenAI FunctionCallbackWrapper2IT auto-config tests. * Add ChatClientTest mockito testing. * Add ChatModel#getDefaultOptions(), and remove @FunctionalInterface * ChatModel enums extend the new ModelDescription interface. * Implement fromOptions copy method in every ChatOptions implementation. * Extend ChatClient to use the model default options if not provided explicitly. * Update readme to provide guidance on how to adapt to breaking changes. Co-authored-by: Christian Tzolov <ctzolov@vmware.com> Co-authored-by: Mark Pollack <mpollack@vmware.com>
This commit is contained in:
@@ -24,7 +24,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.AbstractEmbeddingClient;
|
||||
import org.springframework.ai.embedding.AbstractEmbeddingModel;
|
||||
import org.springframework.ai.embedding.Embedding;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
@@ -39,12 +39,12 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* <a href="https://postgresml.org">PostgresML</a> EmbeddingClient
|
||||
* <a href="https://postgresml.org">PostgresML</a> EmbeddingModel
|
||||
*
|
||||
* @author Toshiaki Maki
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class PostgresMlEmbeddingClient extends AbstractEmbeddingClient implements InitializingBean {
|
||||
public class PostgresMlEmbeddingModel extends AbstractEmbeddingModel implements InitializingBean {
|
||||
|
||||
public static final String DEFAULT_TRANSFORMER_MODEL = "distilbert-base-uncased";
|
||||
|
||||
@@ -83,16 +83,16 @@ public class PostgresMlEmbeddingClient extends AbstractEmbeddingClient implement
|
||||
* a constructor
|
||||
* @param jdbcTemplate JdbcTemplate
|
||||
*/
|
||||
public PostgresMlEmbeddingClient(JdbcTemplate jdbcTemplate) {
|
||||
public PostgresMlEmbeddingModel(JdbcTemplate jdbcTemplate) {
|
||||
this(jdbcTemplate, PostgresMlEmbeddingOptions.builder().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* a PostgresMlEmbeddingClient constructor
|
||||
* a PostgresMlEmbeddingModel constructor
|
||||
* @param jdbcTemplate JdbcTemplate to use to interact with the database.
|
||||
* @param options PostgresMlEmbeddingOptions to configure the client.
|
||||
*/
|
||||
public PostgresMlEmbeddingClient(JdbcTemplate jdbcTemplate, PostgresMlEmbeddingOptions options) {
|
||||
public PostgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, PostgresMlEmbeddingOptions options) {
|
||||
Assert.notNull(jdbcTemplate, "jdbc template must not be null.");
|
||||
Assert.notNull(options, "options must not be null.");
|
||||
Assert.notNull(options.getTransformer(), "transformer must not be null.");
|
||||
@@ -110,7 +110,7 @@ public class PostgresMlEmbeddingClient extends AbstractEmbeddingClient implement
|
||||
* @param transformer huggingface sentence-transformer name
|
||||
*/
|
||||
@Deprecated(since = "0.8.0", forRemoval = true)
|
||||
public PostgresMlEmbeddingClient(JdbcTemplate jdbcTemplate, String transformer) {
|
||||
public PostgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, String transformer) {
|
||||
this(jdbcTemplate, transformer, VectorType.PG_ARRAY);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public class PostgresMlEmbeddingClient extends AbstractEmbeddingClient implement
|
||||
* @param vectorType vector type in PostgreSQL
|
||||
*/
|
||||
@Deprecated(since = "0.8.0", forRemoval = true)
|
||||
public PostgresMlEmbeddingClient(JdbcTemplate jdbcTemplate, String transformer, VectorType vectorType) {
|
||||
public PostgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, String transformer, VectorType vectorType) {
|
||||
this(jdbcTemplate, transformer, vectorType, Map.of(), MetadataMode.EMBED);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public class PostgresMlEmbeddingClient extends AbstractEmbeddingClient implement
|
||||
* @param kwargs optional arguments
|
||||
*/
|
||||
@Deprecated(since = "0.8.0", forRemoval = true)
|
||||
public PostgresMlEmbeddingClient(JdbcTemplate jdbcTemplate, String transformer, VectorType vectorType,
|
||||
public PostgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, String transformer, VectorType vectorType,
|
||||
Map<String, Object> kwargs, MetadataMode metadataMode) {
|
||||
Assert.notNull(jdbcTemplate, "jdbc template must not be null.");
|
||||
Assert.notNull(transformer, "transformer must not be null.");
|
||||
@@ -24,7 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.postgresml.PostgresMlEmbeddingClient.VectorType;
|
||||
import org.springframework.ai.postgresml.PostgresMlEmbeddingModel.VectorType;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
@@ -36,7 +36,7 @@ public class PostgresMlEmbeddingOptions implements EmbeddingOptions {
|
||||
/**
|
||||
* The Huggingface transformer model to use for the embedding.
|
||||
*/
|
||||
private @JsonProperty("transformer") String transformer = PostgresMlEmbeddingClient.DEFAULT_TRANSFORMER_MODEL;
|
||||
private @JsonProperty("transformer") String transformer = PostgresMlEmbeddingModel.DEFAULT_TRANSFORMER_MODEL;
|
||||
|
||||
/**
|
||||
* PostgresML vector type to use for the embedding.
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
import org.springframework.ai.embedding.EmbeddingRequest;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.ai.postgresml.PostgresMlEmbeddingClient.VectorType;
|
||||
import org.springframework.ai.postgresml.PostgresMlEmbeddingModel.VectorType;
|
||||
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
|
||||
@@ -56,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@Testcontainers
|
||||
@Disabled("Disabled from automatic execution, as it requires an excessive amount of memory (over 9GB)!")
|
||||
class PostgresMlEmbeddingClientIT {
|
||||
class PostgresMlEmbeddingModelIT {
|
||||
|
||||
@Container
|
||||
@ServiceConnection
|
||||
@@ -80,51 +80,51 @@ class PostgresMlEmbeddingClientIT {
|
||||
|
||||
@Test
|
||||
void embed() {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate);
|
||||
embeddingClient.afterPropertiesSet();
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate);
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
List<Double> embed = embeddingClient.embed("Hello World!");
|
||||
List<Double> embed = embeddingModel.embed("Hello World!");
|
||||
|
||||
assertThat(embed).hasSize(768);
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedWithPgVector() {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("distilbert-base-uncased")
|
||||
.withVectorType(PostgresMlEmbeddingClient.VectorType.PG_VECTOR)
|
||||
.withVectorType(PostgresMlEmbeddingModel.VectorType.PG_VECTOR)
|
||||
.build());
|
||||
embeddingClient.afterPropertiesSet();
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
List<Double> embed = embeddingClient.embed(new Document("Hello World!"));
|
||||
List<Double> embed = embeddingModel.embed(new Document("Hello World!"));
|
||||
|
||||
assertThat(embed).hasSize(768);
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedWithDifferentModel() {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingOptions.builder().withTransformer("intfloat/e5-small").build());
|
||||
embeddingClient.afterPropertiesSet();
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
List<Double> embed = embeddingClient.embed(new Document("Hello World!"));
|
||||
List<Double> embed = embeddingModel.embed(new Document("Hello World!"));
|
||||
|
||||
assertThat(embed).hasSize(384);
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedWithKwargs() {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("distilbert-base-uncased")
|
||||
.withVectorType(PostgresMlEmbeddingClient.VectorType.PG_ARRAY)
|
||||
.withVectorType(PostgresMlEmbeddingModel.VectorType.PG_ARRAY)
|
||||
.withKwargs(Map.of("device", "cpu"))
|
||||
.withMetadataMode(MetadataMode.EMBED)
|
||||
.build());
|
||||
embeddingClient.afterPropertiesSet();
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
List<Double> embed = embeddingClient.embed(new Document("Hello World!"));
|
||||
List<Double> embed = embeddingModel.embed(new Document("Hello World!"));
|
||||
|
||||
assertThat(embed).hasSize(768);
|
||||
}
|
||||
@@ -132,14 +132,14 @@ class PostgresMlEmbeddingClientIT {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "PG_ARRAY", "PG_VECTOR" })
|
||||
void embedForResponse(String vectorType) {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("distilbert-base-uncased")
|
||||
.withVectorType(VectorType.valueOf(vectorType))
|
||||
.build());
|
||||
embeddingClient.afterPropertiesSet();
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingClient
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World!", "Spring AI!", "LLM!"));
|
||||
|
||||
assertThat(embeddingResponse).isNotNull();
|
||||
@@ -157,16 +157,16 @@ class PostgresMlEmbeddingClientIT {
|
||||
@Test
|
||||
void embedCallWithRequestOptionsOverride() {
|
||||
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate,
|
||||
PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("distilbert-base-uncased")
|
||||
.withVectorType(VectorType.PG_VECTOR)
|
||||
.build());
|
||||
embeddingClient.afterPropertiesSet();
|
||||
embeddingModel.afterPropertiesSet();
|
||||
|
||||
var request1 = new EmbeddingRequest(List.of("Hello World!", "Spring AI!", "LLM!"), EmbeddingOptions.EMPTY);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingClient.call(request1);
|
||||
EmbeddingResponse embeddingResponse = embeddingModel.call(request1);
|
||||
|
||||
assertThat(embeddingResponse).isNotNull();
|
||||
assertThat(embeddingResponse.getResults()).hasSize(3);
|
||||
@@ -188,7 +188,7 @@ class PostgresMlEmbeddingClientIT {
|
||||
.withKwargs(Map.of("device", "cpu"))
|
||||
.build());
|
||||
|
||||
embeddingResponse = embeddingClient.call(request2);
|
||||
embeddingResponse = embeddingModel.call(request2);
|
||||
|
||||
assertThat(embeddingResponse).isNotNull();
|
||||
assertThat(embeddingResponse.getResults()).hasSize(3);
|
||||
@@ -205,11 +205,11 @@ class PostgresMlEmbeddingClientIT {
|
||||
|
||||
@Test
|
||||
void dimensions() {
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(this.jdbcTemplate);
|
||||
embeddingClient.afterPropertiesSet();
|
||||
Assertions.assertThat(embeddingClient.dimensions()).isEqualTo(768);
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(this.jdbcTemplate);
|
||||
embeddingModel.afterPropertiesSet();
|
||||
Assertions.assertThat(embeddingModel.dimensions()).isEqualTo(768);
|
||||
// cached
|
||||
Assertions.assertThat(embeddingClient.dimensions()).isEqualTo(768);
|
||||
Assertions.assertThat(embeddingModel.dimensions()).isEqualTo(768);
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
@@ -34,8 +34,8 @@ public class PostgresMlEmbeddingOptionsTests {
|
||||
public void defaultOptions() {
|
||||
PostgresMlEmbeddingOptions options = PostgresMlEmbeddingOptions.builder().build();
|
||||
|
||||
assertThat(options.getTransformer()).isEqualTo(PostgresMlEmbeddingClient.DEFAULT_TRANSFORMER_MODEL);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingClient.VectorType.PG_ARRAY);
|
||||
assertThat(options.getTransformer()).isEqualTo(PostgresMlEmbeddingModel.DEFAULT_TRANSFORMER_MODEL);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_ARRAY);
|
||||
assertThat(options.getKwargs()).isEqualTo(Map.of());
|
||||
assertThat(options.getMetadataMode()).isEqualTo(org.springframework.ai.document.MetadataMode.EMBED);
|
||||
}
|
||||
@@ -44,13 +44,13 @@ public class PostgresMlEmbeddingOptionsTests {
|
||||
public void newOptions() {
|
||||
PostgresMlEmbeddingOptions options = PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("intfloat/e5-small")
|
||||
.withVectorType(PostgresMlEmbeddingClient.VectorType.PG_VECTOR)
|
||||
.withVectorType(PostgresMlEmbeddingModel.VectorType.PG_VECTOR)
|
||||
.withMetadataMode(org.springframework.ai.document.MetadataMode.ALL)
|
||||
.withKwargs(Map.of("device", "cpu"))
|
||||
.build();
|
||||
|
||||
assertThat(options.getTransformer()).isEqualTo("intfloat/e5-small");
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingClient.VectorType.PG_VECTOR);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_VECTOR);
|
||||
assertThat(options.getKwargs()).isEqualTo(Map.of("device", "cpu"));
|
||||
assertThat(options.getMetadataMode()).isEqualTo(org.springframework.ai.document.MetadataMode.ALL);
|
||||
}
|
||||
@@ -59,37 +59,37 @@ public class PostgresMlEmbeddingOptionsTests {
|
||||
public void mergeOptions() {
|
||||
|
||||
var jdbcTemplate = Mockito.mock(JdbcTemplate.class);
|
||||
PostgresMlEmbeddingClient embeddingClient = new PostgresMlEmbeddingClient(jdbcTemplate);
|
||||
PostgresMlEmbeddingModel embeddingModel = new PostgresMlEmbeddingModel(jdbcTemplate);
|
||||
|
||||
PostgresMlEmbeddingOptions options = embeddingClient.mergeOptions(EmbeddingOptions.EMPTY);
|
||||
PostgresMlEmbeddingOptions options = embeddingModel.mergeOptions(EmbeddingOptions.EMPTY);
|
||||
|
||||
// Default options
|
||||
assertThat(options.getTransformer()).isEqualTo(PostgresMlEmbeddingClient.DEFAULT_TRANSFORMER_MODEL);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingClient.VectorType.PG_ARRAY);
|
||||
assertThat(options.getTransformer()).isEqualTo(PostgresMlEmbeddingModel.DEFAULT_TRANSFORMER_MODEL);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_ARRAY);
|
||||
assertThat(options.getKwargs()).isEqualTo(Map.of());
|
||||
assertThat(options.getMetadataMode()).isEqualTo(org.springframework.ai.document.MetadataMode.EMBED);
|
||||
|
||||
// Partial override
|
||||
options = embeddingClient.mergeOptions(PostgresMlEmbeddingOptions.builder()
|
||||
options = embeddingModel.mergeOptions(PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("intfloat/e5-small")
|
||||
.withKwargs(Map.of("device", "cpu"))
|
||||
.build());
|
||||
|
||||
assertThat(options.getTransformer()).isEqualTo("intfloat/e5-small");
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingClient.VectorType.PG_ARRAY); // Default
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_ARRAY); // Default
|
||||
assertThat(options.getKwargs()).isEqualTo(Map.of("device", "cpu"));
|
||||
assertThat(options.getMetadataMode()).isEqualTo(org.springframework.ai.document.MetadataMode.EMBED); // Default
|
||||
|
||||
// Complete override
|
||||
options = embeddingClient.mergeOptions(PostgresMlEmbeddingOptions.builder()
|
||||
options = embeddingModel.mergeOptions(PostgresMlEmbeddingOptions.builder()
|
||||
.withTransformer("intfloat/e5-small")
|
||||
.withVectorType(PostgresMlEmbeddingClient.VectorType.PG_VECTOR)
|
||||
.withVectorType(PostgresMlEmbeddingModel.VectorType.PG_VECTOR)
|
||||
.withMetadataMode(org.springframework.ai.document.MetadataMode.ALL)
|
||||
.withKwargs(Map.of("device", "cpu"))
|
||||
.build());
|
||||
|
||||
assertThat(options.getTransformer()).isEqualTo("intfloat/e5-small");
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingClient.VectorType.PG_VECTOR);
|
||||
assertThat(options.getVectorType()).isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_VECTOR);
|
||||
assertThat(options.getKwargs()).isEqualTo(Map.of("device", "cpu"));
|
||||
assertThat(options.getMetadataMode()).isEqualTo(org.springframework.ai.document.MetadataMode.ALL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user