Skip to content

Commit

Permalink
Function calling sample with Ollama
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasVitale committed Jul 25, 2024
1 parent ff0c4af commit 5d774da
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 0 deletions.
45 changes: 45 additions & 0 deletions 07-function-calling/function-calling-ollama/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Function Calling: Ollama

Function calling via Ollama.

## Running the application

The application relies on Ollama for providing LLMs. You can either run Ollama locally on your laptop, or rely on the Testcontainers support in Spring Boot to spin up an Ollama service automatically.

### Ollama as a native application

First, make sure you have [Ollama](https://ollama.ai) installed on your laptop.
Then, use Ollama to run the _mistral_ large language model. That's what we'll use in this example.

```shell
ollama run mistral
```

Finally, run the Spring Boot application.

```shell
./gradlew bootRun
```

### Ollama as a dev service with Testcontainers

The application relies on the native Testcontainers support in Spring Boot to spin up an Ollama service with a _mistral_ model at startup time.

```shell
./gradlew bootTestRun
```

## Calling the application

You can now call the application that will use Ollama and _mistral_ to call functions in order to answer questions.
This example uses [httpie](https://httpie.io) to send HTTP requests.

```shell
http :8080/chat/function
```

Try passing your custom prompt and check the result.

```shell
http :8080/chat/function authorName=="Philip Pullman"
```
39 changes: 39 additions & 0 deletions 07-function-calling/function-calling-ollama/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
id 'java'
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

group = 'com.thomasvitale'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(22)
}
}

repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
implementation platform("org.springframework.ai:spring-ai-bom:${springAiVersion}")

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "org.springframework.ai:spring-ai-ollama-spring-boot-starter"

testAndDevelopmentOnly 'org.springframework.boot:spring-boot-devtools'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.springframework.ai:spring-ai-spring-boot-testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:ollama'
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.thomasvitale.ai.spring;

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class BookService {

private static final Map<Integer,Book> books = new ConcurrentHashMap<>();

static {
books.put(1, new Book("His Dark Materials", "Philip Pullman"));
books.put(2, new Book("Narnia", "C.S. Lewis"));
books.put(3, new Book("The Hobbit", "J.R.R. Tolkien"));
books.put(4, new Book("The Lord of The Rings", "J.R.R. Tolkien"));
books.put(5, new Book("The Silmarillion", "J.R.R. Tolkien"));
}

List<Book> getBooksByAuthor(Author author) {
return books.values().stream()
.filter(book -> author.name().equals(book.author()))
.toList();
}

public record Book(String title, String author) {}
public record Author(String name) {}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.thomasvitale.ai.spring;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
class ChatController {

private final ChatService chatService;

ChatController(ChatService chatService) {
this.chatService = chatService;
}

@GetMapping("/chat/function")
String chat(@RequestParam(defaultValue = "J.R.R. Tolkien") String authorName) {
return chatService.getAvailableBooksBy(authorName);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.thomasvitale.ai.spring;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
class ChatService {

private final ChatClient chatClient;

ChatService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}

String getAvailableBooksBy(String authorName) {
var userPromptTemplate = "What books written by {author} are available to read?";
return chatClient.prompt()
.user(userSpec -> userSpec
.text(userPromptTemplate)
.param("author", authorName)
)
.functions("booksByAuthor")
.call()
.content();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.thomasvitale.ai.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FunctionCallingOllamaApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionCallingOllamaApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.thomasvitale.ai.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

import java.util.List;
import java.util.function.Function;

@Configuration(proxyBeanMethods = false)
public class Functions {

@Bean
@Description("Get the list of available books written by the given author")
public Function<BookService.Author, List<BookService.Book>> booksByAuthor(BookService bookService) {
return bookService::getBooksByAuthor;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
spring:
ai:
ollama:
chat:
options:
model: mistral
temperature: 0.7
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.thomasvitale.ai.spring;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class FunctionCallingOllamaApplicationTests {

@Test
void contextLoads() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.thomasvitale.ai.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
public class TestFunctionCallingOllamaApplication {

@Bean
@RestartScope
@ServiceConnection
OllamaContainer ollama() {
return new OllamaContainer(DockerImageName.parse("ghcr.io/thomasvitale/ollama-mistral")
.asCompatibleSubstituteFor("ollama/ollama"));
}

public static void main(String[] args) {
SpringApplication.from(FunctionCallingOllamaApplication::main).with(TestFunctionCallingOllamaApplication.class).run(args);
}

}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ _Coming soon_
| Project | Description |
|--------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| [function-calling-mistral-ai](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/07-function-calling/function-calling-mistral-ai) | Function calling with LLMs via Mistral AI. |
| [function-calling-ollama](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/07-function-calling/function-calling-ollama) | Function calling with LLMs via Ollama. |
| [function-calling-openai](https://github.com/ThomasVitale/llm-apps-java-spring-ai/tree/main/07-function-calling/function-calling-openai) | Function calling with LLMs via OpenAI. |

### 8. Image Models
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ include '05-etl-pipeline:document-transformers-metadata-ollama'
include '05-etl-pipeline:document-transformers-splitters-ollama'

include '07-function-calling:function-calling-mistral-ai'
include '07-function-calling:function-calling-ollama'
include '07-function-calling:function-calling-openai'

include '08-image-models:image-models-openai'
Expand Down

0 comments on commit 5d774da

Please sign in to comment.