Skip to content

Commit

Permalink
Adds Anthropic Connector for Anthropic LLM support
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristophScn committed Jun 11, 2024
1 parent 1c9a019 commit 09c9295
Show file tree
Hide file tree
Showing 13 changed files with 518 additions and 1 deletion.
51 changes: 51 additions & 0 deletions connectors/AnthropicConnector/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-anthropic</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,35 @@
package de.l3s.interweb.connector.anthropic;

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.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

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

@Path("")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "anthropic")
@ClientHeaderParam(name = "x-api-key", value = "${connector.anthropic.apikey}")
public interface AnthropicClient {

/**
* Anthropic Completion API
* https://docs.anthropic.com/en/api/messages
*/
@POST
@Path("/v1/messages")
Uni<CompletionResponse> chatCompletions(@HeaderParam("anthropic-version") String version, CompletionBody body);

@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,84 @@
package de.l3s.interweb.connector.anthropic;

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

import jakarta.enterprise.context.Dependent;

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

import de.l3s.interweb.connector.anthropic.entity.AnthropicContent;
import de.l3s.interweb.connector.anthropic.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;

@Dependent
public class AnthropicConnector implements CompletionConnector {

private static final String version = "2023-06-01";
private static final Map<String, UsagePrice> models = Map.of(
"claude-3-opus-20240229", new UsagePrice(0.015, 0.075),
"claude-3-sonnet-20240229", new UsagePrice(0.003, 0.015),
"claude-3-haiku-20240307", new UsagePrice(0.00025, 0.00125)
);

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

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

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

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

@RestClient
AnthropicClient anthropic;

@Override
public Uni<CompletionResults> complete(CompletionQuery query) throws ConnectorException {
return anthropic.chatCompletions(version, new CompletionBody(query.getModel(), query)).map(response -> {
CompletionResults results = new CompletionResults();
results.setModel(query.getModel());
results.setCreated(Instant.now());

List<Choice> choices = new ArrayList<>();
Choice choice = new Choice();
AnthropicContent content = response.getContent().get(0);
Message message = new Message();
message.setContent(content.getText());
message.setRole(Message.Role.assistant);
choice.setMessage(message);
choice.setIndex(0);
choice.setFinishReason(response.getStopReason());
choices.add(choice);
results.setChoices(choices);

Usage usage = new Usage();
usage.setPromptTokens(response.getUsage().getInputTokens());
usage.setCompletionTokens(response.getUsage().getOutputTokens());
usage.setTotalTokens(response.getUsage().getInputTokens() + response.getUsage().getOutputTokens());
results.setUsage(usage);
return results;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.l3s.interweb.connector.anthropic.entity;

import io.quarkus.runtime.annotations.RegisterForReflection;


@RegisterForReflection
public class AnthropicContent {
private String type;
private String text;

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.l3s.interweb.connector.anthropic.entity;

import io.quarkus.runtime.annotations.RegisterForReflection;

import com.fasterxml.jackson.annotation.JsonProperty;

@RegisterForReflection
public class AnthropicUsage {

@JsonProperty("input_tokens")
private int inputTokens;
@JsonProperty("output_tokens")
private int outputTokens;

public int getInputTokens() {
return inputTokens;
}

public void setInputTokens(int inputTokens) {
this.inputTokens = inputTokens;
}

public int getOutputTokens() {
return outputTokens;
}

public void setOutputTokens(int outputTokens) {
this.outputTokens = outputTokens;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package de.l3s.interweb.connector.anthropic.entity;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.quarkus.runtime.annotations.RegisterForReflection;

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

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

private List<CompletionMessage> messages;

private String model;

private String system;

private Double temperature;

@JsonProperty("top_p")
private Double topP;

@JsonProperty("max_tokens")
private Integer maxTokens;

public CompletionBody(String model, CompletionQuery query) {
this.model = model;
System.out.println("model: " + model);
if (this.model == null) {
throw new IllegalArgumentException("model must be set");
}

this.messages = query.getMessages().stream().filter(
m -> m.getRole() != Role.system
).map(CompletionMessage::new).toList();
this.system = query.getMessages().stream().filter(
m -> m.getRole() == Role.system
).findFirst().map(Message::getContent).orElse(null);

this.temperature = query.getTemperature();
this.topP = query.getTopP();
this.maxTokens = query.getMaxTokens();

if (this.maxTokens == null) {
throw new IllegalArgumentException("maxTokens must be set");
}
}

public String getModel() {
return model;
}

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

public String getSystem() {
return system;
}

public Double getTemperature() {
return temperature;
}

public Double getTopP() {
return topP;
}

public Integer getMaxTokens() {
return maxTokens;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package de.l3s.interweb.connector.anthropic.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;

import io.quarkus.runtime.annotations.RegisterForReflection;

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

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

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

public String getRole() {
return role;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
}
Loading

0 comments on commit 09c9295

Please sign in to comment.