mirror of
https://github.com/amithkoujalgi/ollama4j.git
synced 2025-09-16 11:48:58 +02:00
Add WeatherTool and integration test with auth proxy
Introduces WeatherTool for fetching weather data via OpenWeatherMap API and its tool specification. Adds an integration test (WithAuth) using Testcontainers to verify OllamaAPI connectivity through an NGINX proxy with bearer token authentication. Also updates pom.xml to include the testcontainers-nginx dependency and minor improvements to OllamaAPI for request headers and Javadoc formatting. TypewriterTextarea now supports text alignment, with homepage header using center alignment.
This commit is contained in:
parent
339f788832
commit
54d8cf4cd9
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
const TypewriterTextarea = ({ textContent, typingSpeed = 50, pauseBetweenSentences = 1000, height = '200px', width = '100%' }) => {
|
const TypewriterTextarea = ({ textContent, typingSpeed = 50, pauseBetweenSentences = 1000, height = '200px', width = '100%', align = 'left' }) => {
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const [sentenceIndex, setSentenceIndex] = useState(0);
|
const [sentenceIndex, setSentenceIndex] = useState(0);
|
||||||
const [charIndex, setCharIndex] = useState(0);
|
const [charIndex, setCharIndex] = useState(0);
|
||||||
@ -56,8 +56,10 @@ const TypewriterTextarea = ({ textContent, typingSpeed = 50, pauseBetweenSentenc
|
|||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
backgroundColor: '#f4f4f4',
|
backgroundColor: '#f4f4f4',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
|
textAlign: align,
|
||||||
resize: 'none',
|
resize: 'none',
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
|
color: 'black',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -32,6 +32,7 @@ function HomepageHeader() {
|
|||||||
pauseBetweenSentences={1200}
|
pauseBetweenSentences={1200}
|
||||||
height='130px'
|
height='130px'
|
||||||
width='100%'
|
width='100%'
|
||||||
|
align='center'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttons} >
|
<div className={styles.buttons} >
|
||||||
|
6
pom.xml
6
pom.xml
@ -223,6 +223,12 @@
|
|||||||
<version>1.20.2</version>
|
<version>1.20.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>nginx</artifactId>
|
||||||
|
<version>1.20.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
@ -51,7 +51,7 @@ import java.util.stream.Collectors;
|
|||||||
/**
|
/**
|
||||||
* The base Ollama API class.
|
* The base Ollama API class.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "DuplicatedCode", "resource" })
|
@SuppressWarnings({"DuplicatedCode", "resource"})
|
||||||
public class OllamaAPI {
|
public class OllamaAPI {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OllamaAPI.class);
|
private static final Logger logger = LoggerFactory.getLogger(OllamaAPI.class);
|
||||||
@ -789,8 +789,9 @@ public class OllamaAPI {
|
|||||||
String jsonData = Utils.getObjectMapper().writeValueAsString(requestBody);
|
String jsonData = Utils.getObjectMapper().writeValueAsString(requestBody);
|
||||||
HttpClient httpClient = HttpClient.newHttpClient();
|
HttpClient httpClient = HttpClient.newHttpClient();
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
HttpRequest request = getRequestBuilderDefault(uri)
|
||||||
.header("Content-Type", "application/json")
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-type", "application/json")
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
|
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package io.github.ollama4j.tools.sampletools;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.github.ollama4j.tools.Tools;
|
||||||
|
|
||||||
|
public class WeatherTool {
|
||||||
|
private String openWeatherMapAPIKey = null;
|
||||||
|
|
||||||
|
public WeatherTool(String openWeatherMapAPIKey) {
|
||||||
|
this.openWeatherMapAPIKey = openWeatherMapAPIKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentWeather(Map<String, Object> arguments) {
|
||||||
|
String city = (String) arguments.get("cityName");
|
||||||
|
System.out.println("Finding weather for city: " + city);
|
||||||
|
|
||||||
|
String url = String.format("https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric",
|
||||||
|
city,
|
||||||
|
this.openWeatherMapAPIKey);
|
||||||
|
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode root = mapper.readTree(response.body());
|
||||||
|
JsonNode main = root.path("main");
|
||||||
|
double temperature = main.path("temp").asDouble();
|
||||||
|
String description = root.path("weather").get(0).path("description").asText();
|
||||||
|
return String.format("Weather in %s: %.1f°C, %s", city, temperature, description);
|
||||||
|
} else {
|
||||||
|
return "Could not retrieve weather data for " + city + ". Status code: "
|
||||||
|
+ response.statusCode();
|
||||||
|
}
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "Error retrieving weather data: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tools.ToolSpecification getSpecification() {
|
||||||
|
return Tools.ToolSpecification.builder()
|
||||||
|
.functionName("weather-reporter")
|
||||||
|
.functionDescription(
|
||||||
|
"You are a tool who simply finds the city name from the user's message input/query about weather.")
|
||||||
|
.toolFunction(this::getCurrentWeather)
|
||||||
|
.toolPrompt(
|
||||||
|
Tools.PromptFuncDefinition.builder()
|
||||||
|
.type("prompt")
|
||||||
|
.function(
|
||||||
|
Tools.PromptFuncDefinition.PromptFuncSpec
|
||||||
|
.builder()
|
||||||
|
.name("get-city-name")
|
||||||
|
.description("Get the city name")
|
||||||
|
.parameters(
|
||||||
|
Tools.PromptFuncDefinition.Parameters
|
||||||
|
.builder()
|
||||||
|
.type("object")
|
||||||
|
.properties(
|
||||||
|
Map.of(
|
||||||
|
"cityName",
|
||||||
|
Tools.PromptFuncDefinition.Property
|
||||||
|
.builder()
|
||||||
|
.type("string")
|
||||||
|
.description(
|
||||||
|
"The name of the city. e.g. Bengaluru")
|
||||||
|
.required(true)
|
||||||
|
.build()))
|
||||||
|
.required(java.util.List
|
||||||
|
.of("cityName"))
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
126
src/test/java/io/github/ollama4j/integrationtests/WithAuth.java
Normal file
126
src/test/java/io/github/ollama4j/integrationtests/WithAuth.java
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package io.github.ollama4j.integrationtests;
|
||||||
|
|
||||||
|
import io.github.ollama4j.OllamaAPI;
|
||||||
|
import io.github.ollama4j.samples.AnnotatedTool;
|
||||||
|
import io.github.ollama4j.tools.annotations.OllamaToolService;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.NginxContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.ollama.OllamaContainer;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@OllamaToolService(providers = {AnnotatedTool.class})
|
||||||
|
@TestMethodOrder(OrderAnnotation.class)
|
||||||
|
@SuppressWarnings({"HttpUrlsUsage", "SpellCheckingInspection", "resource", "ResultOfMethodCallIgnored"})
|
||||||
|
public class WithAuth {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(WithAuth.class);
|
||||||
|
private static final int NGINX_PORT = 80;
|
||||||
|
private static final int OLLAMA_INTERNAL_PORT = 11434;
|
||||||
|
private static final String OLLAMA_VERSION = "0.6.1";
|
||||||
|
|
||||||
|
private static OllamaContainer ollama;
|
||||||
|
private static GenericContainer<?> nginx;
|
||||||
|
private static OllamaAPI api;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
ollama = createOllamaContainer();
|
||||||
|
ollama.start();
|
||||||
|
|
||||||
|
nginx = createNginxContainer(ollama.getMappedPort(OLLAMA_INTERNAL_PORT));
|
||||||
|
nginx.start();
|
||||||
|
|
||||||
|
LOG.info("Using Testcontainer Ollama host...");
|
||||||
|
|
||||||
|
api = new OllamaAPI("http://" + nginx.getHost() + ":" + nginx.getMappedPort(NGINX_PORT));
|
||||||
|
api.setRequestTimeoutSeconds(120);
|
||||||
|
api.setVerbose(true);
|
||||||
|
api.setNumberOfRetriesForModelPull(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OllamaContainer createOllamaContainer() {
|
||||||
|
OllamaContainer container = new OllamaContainer("ollama/ollama:" + OLLAMA_VERSION);
|
||||||
|
container.addExposedPort(OLLAMA_INTERNAL_PORT);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateNginxConfig(int ollamaPort) {
|
||||||
|
return String.format("events {}\n" +
|
||||||
|
"\n" +
|
||||||
|
"http {\n" +
|
||||||
|
" server {\n" +
|
||||||
|
" listen 80;\n" +
|
||||||
|
"\n" +
|
||||||
|
" location / {\n" +
|
||||||
|
" set $auth_header $http_authorization;\n" +
|
||||||
|
"\n" +
|
||||||
|
" if ($auth_header != \"Bearer secret-token\") {\n" +
|
||||||
|
" return 401;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"\n" +
|
||||||
|
" proxy_pass http://host.docker.internal:%s/;\n" +
|
||||||
|
" proxy_set_header Host $host;\n" +
|
||||||
|
" proxy_set_header X-Real-IP $remote_addr;\n" +
|
||||||
|
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n" +
|
||||||
|
" proxy_set_header X-Forwarded-Proto $scheme;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n", ollamaPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericContainer<?> createNginxContainer(int ollamaPort) {
|
||||||
|
File nginxConf;
|
||||||
|
|
||||||
|
try {
|
||||||
|
File tempDir = new File(System.getProperty("java.io.tmpdir"), "nginx-auth");
|
||||||
|
if (!tempDir.exists()) tempDir.mkdirs();
|
||||||
|
|
||||||
|
nginxConf = new File(tempDir, "nginx.conf");
|
||||||
|
try (FileWriter writer = new FileWriter(nginxConf)) {
|
||||||
|
writer.write(generateNginxConfig(ollamaPort));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NginxContainer<>(DockerImageName.parse("nginx:1.23.4-alpine"))
|
||||||
|
.withExposedPorts(NGINX_PORT)
|
||||||
|
.withCopyFileToContainer(
|
||||||
|
MountableFile.forHostPath(nginxConf.getAbsolutePath()),
|
||||||
|
"/etc/nginx/nginx.conf"
|
||||||
|
)
|
||||||
|
.withExtraHost("host.docker.internal", "host-gateway")
|
||||||
|
.waitingFor(
|
||||||
|
Wait.forHttp("/")
|
||||||
|
.forStatusCode(401)
|
||||||
|
.withStartupTimeout(Duration.ofSeconds(30))
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to create nginx.conf", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
void testEndpoint() throws InterruptedException {
|
||||||
|
String ollamaUrl = "http://" + ollama.getHost() + ":" + ollama.getMappedPort(OLLAMA_INTERNAL_PORT);
|
||||||
|
String nginxUrl = "http://" + nginx.getHost() + ":" + nginx.getMappedPort(NGINX_PORT);
|
||||||
|
System.out.printf("Ollama service at %s is now accessible through the Nginx proxy at %s%n", ollamaUrl, nginxUrl);
|
||||||
|
api.setBearerAuth("secret-token");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
assertTrue(api.ping(), "OllamaAPI failed to ping through NGINX with auth.");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user