Auto-configuration and starter for Ollama

- Adds auto-configuration and a starter for Ollama.
 - Add testconteiner orca-mini integration test.
 - Improve code style and documentation.

Resolves #68, Resolves #69
This commit is contained in:
Tommy Ludwig
2023-11-16 23:23:20 +09:00
committed by Christian Tzolov
parent 820eb5f0ab
commit 6bdea65cd5
9 changed files with 354 additions and 3 deletions

View File

@@ -22,6 +22,7 @@
<module>spring-ai-spring-boot-autoconfigure</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-openai</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-azure-openai</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-ollama</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-transformers-embedding</module>
<module>spring-ai-docs</module>
<module>vector-stores/spring-ai-pgvector-store</module>

View File

@@ -1,9 +1,56 @@
## Ollama
# Ollama
Ollama lets you ge tup an running with large language models locally
Ollama lets you get up and running with large language models locally.
Refer to the official [README](https://github.com/jmorganca/ollama) to get started.
Note, installing `ollama run llama2` will download a 4GB docker image.
You can run the disabled test in `OllamaClientTests.java` to kick the tires.
You can run the disabled test in `OllamaClientTests.java` to kick the tires.
## How to use
Add the `spring-ai-ollama` dependency to your project's pom:
```xml
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-ollama</artifactId>
<version>0.7.1-SNAPSHOT</version>
</dependency>
```
then create an client and use generate response:
```java
var ollamaClient = new OllamaClient("http://127.0.0.1:11434", "llama2",
ollamaResult -> {
if (ollamaResult.getDone()) {
....
}
});
AiResponse aiResponse = ollamaClient.generate(new Prompt("Hello"));
```
### Spring Boot Starter
For convenience you can opt for the Ollama Boot starter.
For this add the following dependency:
```xml
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>0.7.1-SNAPSHOT</version>
</dependency>
```
and use the `spring.ai.ollama.*` properties to configure it if you want to use something other than the default values.
The complete list of supported properties are:
| Property | Description | Default |
| -------- | ----- | ----- |
| spring.ai.ollama.base-url | Base URL where Ollama API server is running. | `http://localhost:11434` |
| spring.ai.ollama.model | Language model to use. | `llama2` |

View File

@@ -49,6 +49,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-ollama</artifactId>
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>
<!-- Transformers Embedding Client -->
<dependency>
<groupId>org.springframework.experimental.ai</groupId>

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2023-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.ollama;
import org.springframework.ai.ollama.client.OllamaClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(OllamaClient.class)
@EnableConfigurationProperties(OllamaProperties.class)
public class OllamaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OllamaClient ollamaClient(OllamaProperties properties) {
return new OllamaClient(properties.getBaseUrl(), properties.getModel());
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2023-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.ollama;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(OllamaProperties.CONFIG_PREFIX)
public class OllamaProperties {
public static final String CONFIG_PREFIX = "spring.ai.ollama";
/**
* Base URL where Ollama API server is running.
*/
private String baseUrl = "http://localhost:11434";
/**
* Language model to use.
*/
private String model = "llama2";
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -1,3 +1,4 @@
org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.pgvector.PgVectorStoreAutoConfiguration

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2023-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.ollama;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.ai.ollama.client.OllamaClient;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@Disabled("As it downloads the 3GB 'orca-mini' it can take couple of minutes to initialize.")
@Testcontainers
public class OllamaAutoConfigurationIT {
private static final Log logger = LogFactory.getLog(OllamaAutoConfigurationIT.class);
@Container
static GenericContainer<?> ollamaContainer = new GenericContainer<>("ollama/ollama").withExposedPorts(11434);
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.ai.ollama.baseUrl=http://localhost:" + ollamaContainer.getMappedPort(11434),
"spring.ai.ollama.model=orca-mini")
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
@BeforeAll
public static void beforeAll() {
logger.info("Start pulling the 'orca-mini' model (3GB) ... would take several minutes ...");
new RestTemplate().postForLocation("http://localhost:{port}/api/pull",
new HttpEntity<>(Map.of("name", "orca-mini")), ollamaContainer.getMappedPort(11434));
logger.info("orca-mini pulling competed!");
}
@Test
void generate() {
contextRunner.run(context -> {
OllamaClient client = context.getBean(OllamaClient.class);
assertThat(client.getBaseUrl()).isEqualTo("http://localhost:" + ollamaContainer.getMappedPort(11434));
assertThat(client.getModel()).isEqualTo("orca-mini");
String response = client.generate("Hello");
assertThat(response).isNotEmpty();
logger.info("Response: " + response);
});
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2023-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.ollama;
import org.junit.jupiter.api.Test;
import org.springframework.ai.ollama.client.OllamaClient;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
public class OllamaAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
@Test
void defaults() {
contextRunner.run(context -> {
OllamaProperties properties = context.getBean(OllamaProperties.class);
assertThat(properties.getBaseUrl()).isEqualTo("http://localhost:11434");
assertThat(properties.getModel()).isEqualTo("llama2");
OllamaClient client = context.getBean(OllamaClient.class);
assertThat(client.getBaseUrl()).isEqualTo("http://localhost:11434");
assertThat(client.getModel()).isEqualTo("llama2");
});
}
@Test
void overrideProperties() {
contextRunner
.withPropertyValues("spring.ai.ollama.base-url=http://localhost:8080", "spring.ai.ollama.model=myModel")
.run(context -> {
OllamaProperties properties = context.getBean(OllamaProperties.class);
assertThat(properties.getBaseUrl()).isEqualTo("http://localhost:8080");
assertThat(properties.getModel()).isEqualTo("myModel");
OllamaClient client = context.getBean(OllamaClient.class);
assertThat(client.getBaseUrl()).isEqualTo("http://localhost:8080");
assertThat(client.getModel()).isEqualTo("myModel");
});
}
@Test
void customConfig() {
contextRunner.withUserConfiguration(CustomConfig.class).run(context -> {
OllamaClient ollamaClient = context.getBean(OllamaClient.class);
assertThat(ollamaClient.getBaseUrl()).isEqualTo("http://localhost:8080");
assertThat(ollamaClient.getModel()).isEqualTo("myModel");
});
}
@Configuration(proxyBeanMethods = false)
static class CustomConfig {
@Bean
OllamaClient myClient() {
return new OllamaClient("http://localhost:8080", "myModel");
}
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>0.7.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - Ollama</name>
<description>Spring AI Ollama Auto Configuration</description>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<scm>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<connection>git://github.com/spring-projects-experimental/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects-experimental/spring-ai.git</developerConnection>
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-ollama</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>