Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Ollama support #92

Merged
merged 1 commit into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ Interweb currently supports the following data providers:
6. **Ipernity**:
- Search: discover photos and images within one of the largest non-commercial clubs.
- Describe: obtain photo information using its url.
7. **OpenAI**:
7. **Ollama**:
- Interact with open-source LLMs for natural language understanding and generation.
8. **OpenAI**:
- Interact with OpenAI's ChatGPT for natural language understanding and generation.
8. **SlideShare**:
9. **SlideShare**:
- Search: find presentations and documents for various topics.
9. **Vimeo**:
- Search: locate videos created by creative content creators.
- Describe: obtain video information using its url.
10. **YouTube**:
10. **Vimeo**:
- Search: locate videos created by creative content creators.
- Describe: obtain video information using its url.
11. **YouTube**:
- Search: for videos in the largest video hosting platform.
- Describe: obtain detailed information about a video using its url.

Expand Down
51 changes: 51 additions & 0 deletions connectors/OllamaConnector/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>de.l3s.interweb</groupId>
<artifactId>interweb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>connector-ollama</artifactId>
<version>4.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>de.l3s.interweb</groupId>
<artifactId>interweb-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.l3s.interweb.connector.ollama;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import io.quarkus.rest.client.reactive.ClientExceptionMapper;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import de.l3s.interweb.connector.ollama.entity.CompletionResponse;
import de.l3s.interweb.connector.ollama.entity.TagsResponse;
import de.l3s.interweb.connector.ollama.entity.CompletionBody;
import de.l3s.interweb.core.ConnectorException;

@Path("")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "ollama")
public interface OllamaClient {

/**
* Ollama Completion API
* https://github.com/ollama/ollama/blob/main/docs/api.md
*/
@POST
@Path("/api/chat")
Uni<CompletionResponse> chatCompletions(CompletionBody body);

@GET
@Path("/api/tags")
Uni<TagsResponse> tags();

@ClientExceptionMapper
static RuntimeException toException(Response response) {
return new ConnectorException("Remote service responded with HTTP " + response.getStatus(), response.readEntity(String.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package de.l3s.interweb.connector.ollama;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import jakarta.enterprise.context.Dependent;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import de.l3s.interweb.connector.ollama.entity.TagsResponse;
import de.l3s.interweb.connector.ollama.entity.CompletionBody;
import de.l3s.interweb.core.ConnectorException;
import de.l3s.interweb.core.completion.CompletionConnector;
import de.l3s.interweb.core.completion.CompletionQuery;
import de.l3s.interweb.core.completion.CompletionResults;
import de.l3s.interweb.core.completion.Message;
import de.l3s.interweb.core.completion.UsagePrice;
import de.l3s.interweb.core.completion.Usage;
import de.l3s.interweb.core.completion.Choice;

import org.jboss.logging.Logger;

@Dependent
public class OllamaConnector implements CompletionConnector {
private static final Logger log = Logger.getLogger(OllamaConnector.class);

private static final Map<String, UsagePrice> models = new HashMap<>();

@Override
public String getName() {
return "Ollama";
}

@Override
public String getBaseUrl() {
return "https://ollama.com/";
}

@Override
public String[] getModels() {
return models.keySet().toArray(new String[0]);
}

@Override
public UsagePrice getPrice(String model) {
return models.get(model);
}

@RestClient
OllamaClient ollama;

@Override
public Uni<CompletionResults> complete(CompletionQuery query) throws ConnectorException {
return ollama.chatCompletions(new CompletionBody(query)).map(response -> {
Usage usage = new Usage(
response.getPromptEvalCount(),
response.getEvalCount()
);

List<Choice> choices = List.of(
new Choice(
0,
response.getDoneReason(),
new Message(
Message.Role.assistant,
response.getMessage().getContent()
)
)
);

CompletionResults results = new CompletionResults();
results.setModel(response.getModel());
results.setUsage(usage);
results.setChoices(choices);
results.setCreated(Instant.now());
return results;
});
}

@Override
public boolean validate() {
TagsResponse tags;
try {
tags = ollama.tags().await().indefinitely();
} catch (Exception e) {
log.error("Failed to validate Ollama connector", e);
return false;
}

List<String> models = tags.getModels().stream().map(model -> model.getName()).toList();
if (models.isEmpty()) {
log.warn("No models found in Ollama connector");
return false;
}

OllamaConnector.models.clear();
for (String model : models) {
OllamaConnector.models.put(model, new UsagePrice(0, 0));
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.l3s.interweb.connector.ollama.entity;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

import io.quarkus.runtime.annotations.RegisterForReflection;

import de.l3s.interweb.core.completion.CompletionQuery;

@JsonInclude(JsonInclude.Include.NON_NULL)
@RegisterForReflection
public final class CompletionBody {

private String model;

private List<CompletionMessage> messages;

private CompletionOptions options;

private final Boolean stream = false;

public CompletionBody(CompletionQuery query) {
this.model = query.getModel();

this.messages = query.getMessages().stream()
.map(CompletionMessage::new)
.toList();

this.options = new CompletionOptions(query);
}

public String getModel() {
return model;
}

public List<CompletionMessage> getMessages() {
return messages;
}

public CompletionOptions getOptions() {
return options;
}

public Boolean getStream() {
return stream;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.l3s.interweb.connector.ollama.entity;

import io.quarkus.runtime.annotations.RegisterForReflection;

import de.l3s.interweb.core.completion.Message;

@RegisterForReflection
public final class CompletionMessage {
private String role;
private String content;

public CompletionMessage() {
}

public CompletionMessage(Message message) {
this.role = message.getRole().name();
this.content = message.getContent();
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package de.l3s.interweb.connector.ollama.entity;

import com.fasterxml.jackson.annotation.JsonProperty;

import de.l3s.interweb.core.completion.CompletionQuery;


public class CompletionOptions {
private Integer seed;
private String stop;
@JsonProperty("num_predict")
private Integer numPredict;
private Double temperature;
@JsonProperty("top_p")
private Double topP;

public CompletionOptions() {
}

public CompletionOptions(CompletionQuery query) {
this.seed = query.getSeed();
if (query.getStop() != null && query.getStop().length > 0) {
this.stop = query.getStop()[0];
}
this.numPredict = query.getMaxTokens();
this.temperature = query.getTemperature();
this.topP = query.getTopP();
}

public Integer getSeed() {
return seed;
}

public void setSeed(Integer seed) {
this.seed = seed;
}

public String getStop() {
return stop;
}

public void setStop(String stop) {
this.stop = stop;
}

public Integer getNumPredict() {
return numPredict;
}

public void setNumPredict(Integer numPredict) {
this.numPredict = numPredict;
}

public Double getTemperature() {
return temperature;
}

public void setTemperature(Double temperature) {
this.temperature = temperature;
}

public Double getTopP() {
return topP;
}

public void setTopP(Double topP) {
this.topP = topP;
}
}
Loading