-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(BE-174): integrate anthropic api
- Loading branch information
Showing
18 changed files
with
509 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
...ic-client/anthropic-client-core/src/commonMain/kotlin/com/tddworks/anthropic/api/Model.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.tddworks.anthropic.api | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlin.jvm.JvmInline | ||
|
||
/** | ||
* https://docs.anthropic.com/claude/docs/models-overview | ||
* Claude is a family of state-of-the-art large language models developed by Anthropic. Our models are designed to provide you with the best possible experience when interacting with AI, offering a range of capabilities and performance levels to suit your needs and make it easy to deploy high performing, safe, and steerable models. In this guide, we'll introduce you to our latest and greatest models, the Claude 3 family, as well as our legacy models, which are still available for those who need them. | ||
* | ||
*/ | ||
@Serializable | ||
@JvmInline | ||
value class Model(val value: String) { | ||
companion object { | ||
/** | ||
* Most powerful model for highly complex tasks | ||
* Max output length: 4096 tokens | ||
* Cost (Input / Output per MTok^) $15.00 / $75.00 | ||
*/ | ||
val CLAUDE_3_OPUS = Model("claude-3-opus-20240229") | ||
|
||
/** | ||
* Ideal balance of intelligence and speed for enterprise workloads | ||
* Max output length: 4096 tokens | ||
* Cost (Input / Output per MTok^) $3.00 / $15.00 | ||
*/ | ||
val CLAUDE_3_Sonnet = Model("claude-3-sonnet-20240229") | ||
|
||
/** | ||
* Fastest and most compact model for near-instant responsiveness | ||
* Max output length: 4096 tokens | ||
* Cost (Input / Output per MTok^) $0.25 / $1.25 | ||
*/ | ||
val CLAUDE_3_HAIKU = Model("claude-3-haiku-20240307") | ||
|
||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...ore/src/commonMain/kotlin/com/tddworks/anthropic/api/messages/api/CreateMessageRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import com.tddworks.openllm.api.ChatRequest | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.modules.SerializersModule | ||
import kotlinx.serialization.modules.polymorphic | ||
|
||
@Serializable | ||
@ExperimentalSerializationApi | ||
data class CreateMessageRequest( | ||
val messages: List<Message>, | ||
val systemPrompt: String? = null, | ||
) : ChatRequest | ||
|
||
|
53 changes: 53 additions & 0 deletions
53
...re/src/commonMain/kotlin/com/tddworks/anthropic/api/messages/api/CreateMessageResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import com.tddworks.openllm.api.ChatResponse | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* { | ||
* "content": [ | ||
* { | ||
* "text": "Hi! My name is Claude.", | ||
* "type": "text" | ||
* } | ||
* ], | ||
* "id": "msg_013Zva2CMHLNnXjNJJKqJ2EF", | ||
* "model": "claude-3-opus-20240229", | ||
* "role": "assistant", | ||
* "stop_reason": "end_turn", | ||
* "stop_sequence": null, | ||
* "type": "message", | ||
* "usage": { | ||
* "input_tokens": 10, | ||
* "output_tokens": 25 | ||
* } | ||
* } | ||
*/ | ||
@Serializable | ||
data class CreateMessageResponse( | ||
val content: List<ContentMessage>, | ||
val id: String, | ||
val model: String, | ||
val role: String, | ||
@SerialName("stop_reason") | ||
val stopReason: String, | ||
@SerialName("stop_sequence") | ||
val stopSequence: String?, | ||
val type: String, | ||
val usage: Usage, | ||
) : ChatResponse | ||
|
||
|
||
@Serializable | ||
data class ContentMessage(val text: String, val type: String) | ||
|
||
@Serializable | ||
data class Usage( | ||
@SerialName("input_tokens") | ||
val inputTokens: Int, | ||
@SerialName("output_tokens") | ||
val outputTokens: Int, | ||
) | ||
|
||
|
25 changes: 25 additions & 0 deletions
25
...opic-client-core/src/commonMain/kotlin/com/tddworks/anthropic/api/messages/api/Message.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
/** | ||
* https://docs.anthropic.com/claude/reference/messages_post | ||
* Our models are trained to operate on alternating user and assistant conversational turns. When creating a new Message, you specify the prior conversational turns with the messages parameter, and the model then generates the next Message in the conversation. | ||
* | ||
* Each input message must be an object with a role and content. You can specify a single user-role message, or you can include multiple user and assistant messages. The first message must always use the user role. | ||
* | ||
* If the final message uses the assistant role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response. | ||
* | ||
* Example with a single user message: | ||
* | ||
* [{"role": "user", "content": "Hello, Claude"}] | ||
*/ | ||
@Serializable | ||
data class Message( | ||
val role: Role, | ||
val content: String, | ||
) { | ||
companion object { | ||
fun user(content: String) = Message(Role.User, content) | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...thropic-client-core/src/commonMain/kotlin/com/tddworks/anthropic/api/messages/api/Role.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlin.jvm.JvmInline | ||
|
||
/** | ||
* https://docs.anthropic.com/claude/reference/messages_post | ||
* Our models are trained to operate on alternating user and assistant conversational turns. When creating a new Message, you specify the prior conversational turns with the messages parameter, and the model then generates the next Message in the conversation. | ||
*/ | ||
@JvmInline | ||
@Serializable | ||
value class Role(val name: String) { | ||
companion object { | ||
val User: Role = Role("user") | ||
val Assistant: Role = Role("assistant") | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
.../commonMain/kotlin/com/tddworks/anthropic/api/messages/api/internal/DefaultMessagesApi.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.tddworks.anthropic.api.messages.api.internal | ||
|
||
import com.tddworks.common.network.api.ktor.api.HttpRequester | ||
import com.tddworks.common.network.api.ktor.api.performRequest | ||
import com.tddworks.openllm.api.ChatApi | ||
import com.tddworks.openllm.api.ChatRequest | ||
import com.tddworks.openllm.api.ChatResponse | ||
import io.ktor.client.request.* | ||
import io.ktor.http.* | ||
|
||
/** | ||
* * Anthropic Messages API -https://docs.anthropic.com/claude/reference/messages_post | ||
*/ | ||
class DefaultMessagesApi(private val requester: HttpRequester) : ChatApi { | ||
/** | ||
* Create a message. | ||
* @param request Send a structured list of input messages with text and/or image content, and the model will generate the next message in the conversation. | ||
* @return The chat completion. | ||
*/ | ||
override suspend fun chat(request: ChatRequest): ChatResponse { | ||
return requester.performRequest<ChatResponse> { | ||
method = HttpMethod.Post | ||
url(path = CHAT_COMPLETIONS_PATH) | ||
setBody(request) | ||
contentType(ContentType.Application.Json) | ||
} | ||
} | ||
|
||
companion object { | ||
const val CHAT_COMPLETIONS_PATH = "/v1/messages" | ||
} | ||
|
||
} |
45 changes: 45 additions & 0 deletions
45
...rc/commonMain/kotlin/com/tddworks/anthropic/api/messages/api/internal/json/Serializers.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.tddworks.anthropic.api.messages.api.internal.json | ||
|
||
import com.tddworks.anthropic.api.messages.api.CreateMessageRequest | ||
import com.tddworks.anthropic.api.messages.api.CreateMessageResponse | ||
import com.tddworks.openllm.api.ChatRequest | ||
import com.tddworks.openllm.api.ChatResponse | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.KSerializer | ||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer | ||
import kotlinx.serialization.json.JsonElement | ||
import kotlinx.serialization.json.jsonObject | ||
import kotlinx.serialization.modules.SerializersModule | ||
import kotlinx.serialization.modules.polymorphic | ||
|
||
@OptIn(ExperimentalSerializationApi::class) | ||
val chatModule = SerializersModule { | ||
polymorphic(ChatRequest::class) { | ||
subclass(CreateMessageRequest::class, CreateMessageRequest.serializer()) | ||
defaultDeserializer { CreateMessageRequest.serializer() } | ||
} | ||
polymorphic(ChatResponse::class) { | ||
subclass(CreateMessageResponse::class, CreateMessageResponse.serializer()) | ||
defaultDeserializer { ChatMessageSerializer } | ||
} | ||
|
||
// polymorphicDefaultSerializer(ChatResponse::class) { instance -> | ||
// @Suppress("UNCHECKED_CAST") | ||
// when (instance) { | ||
// is CreateMessageResponse -> CreateMessageResponse.serializer() as SerializationStrategy<ChatResponse> | ||
// else -> null | ||
// } | ||
// } | ||
} | ||
|
||
object ChatMessageSerializer : | ||
JsonContentPolymorphicSerializer<ChatResponse>(ChatResponse::class) { | ||
override fun selectDeserializer(element: JsonElement): KSerializer<out ChatResponse> { | ||
val content = element.jsonObject["content"] | ||
|
||
return when { | ||
content != null -> CreateMessageResponse.serializer() | ||
else -> throw IllegalArgumentException("Unknown type of message") | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
...c-client/anthropic-client-core/src/jvmTest/kotlin/com/tddworks/anthropic/api/JsonUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.tddworks.anthropic.api | ||
|
||
import com.tddworks.anthropic.api.messages.api.internal.json.chatModule | ||
import kotlinx.serialization.json.Json | ||
|
||
val prettyJson = Json { // this returns the JsonBuilder | ||
prettyPrint = true | ||
ignoreUnknownKeys = true | ||
// optional: specify indent | ||
prettyPrintIndent = " " | ||
} | ||
|
||
/** | ||
* Represents a JSON object that allows for leniency and ignores unknown keys. | ||
* | ||
* @property isLenient Removes JSON specification restriction (RFC-4627) and makes parser more liberal to the malformed input. In lenient mode quoted boolean literals, and unquoted string literals are allowed. | ||
* Its relaxations can be expanded in the future, so that lenient parser becomes even more permissive to invalid value in the input, replacing them with defaults. | ||
* false by default. | ||
* @property ignoreUnknownKeys Specifies whether encounters of unknown properties in the input JSON should be ignored instead of throwing SerializationException. false by default.. | ||
*/ | ||
internal val JsonLenient = Json { | ||
isLenient = true | ||
ignoreUnknownKeys = true | ||
// https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#class-discriminator-for-polymorphism | ||
classDiscriminator = "#class" | ||
serializersModule = chatModule | ||
} |
42 changes: 42 additions & 0 deletions
42
...ent/anthropic-client-core/src/jvmTest/kotlin/com/tddworks/anthropic/api/MockHttpClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.tddworks.anthropic.api | ||
|
||
|
||
import io.ktor.client.* | ||
import io.ktor.client.engine.mock.* | ||
import io.ktor.client.plugins.* | ||
import io.ktor.client.plugins.contentnegotiation.* | ||
import io.ktor.client.request.* | ||
import io.ktor.http.* | ||
import io.ktor.serialization.kotlinx.* | ||
|
||
/** | ||
* See https://ktor.io/docs/http-client-testing.html#usage | ||
*/ | ||
fun mockHttpClient(mockResponse: String) = HttpClient(MockEngine) { | ||
|
||
val headers = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) | ||
|
||
install(ContentNegotiation) { | ||
register(ContentType.Application.Json, KotlinxSerializationConverter(JsonLenient)) | ||
} | ||
|
||
engine { | ||
addHandler { request -> | ||
if (request.url.encodedPath == "/v1/messages") { | ||
respond(mockResponse, HttpStatusCode.OK, headers) | ||
} else { | ||
error("Unhandled ${request.url.encodedPath}") | ||
} | ||
} | ||
} | ||
|
||
defaultRequest { | ||
url { | ||
protocol = URLProtocol.HTTPS | ||
host = "api.lemonsqueezy.com" | ||
} | ||
|
||
header(HttpHeaders.ContentType, ContentType.Application.Json) | ||
contentType(ContentType.Application.Json) | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...c-client/anthropic-client-core/src/jvmTest/kotlin/com/tddworks/anthropic/api/ModelTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.tddworks.anthropic.api | ||
|
||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
|
||
class ModelTest { | ||
@Test | ||
fun `should return correct latest API model name`() { | ||
assertEquals("claude-3-opus-20240229", Model.CLAUDE_3_OPUS.value) | ||
assertEquals("claude-3-sonnet-20240229", Model.CLAUDE_3_Sonnet.value) | ||
assertEquals("claude-3-haiku-20240307", Model.CLAUDE_3_HAIKU.value) | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...pic-client-core/src/jvmTest/kotlin/com/tddworks/anthropic/api/messages/api/MessageTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
|
||
class MessageTest { | ||
|
||
@Test | ||
fun `should return assistant message name`() { | ||
val assistantMessage = Message(role = Role.Assistant, content = "message") | ||
assertEquals(Role.Assistant, assistantMessage.role) | ||
assertEquals("message", assistantMessage.content) | ||
} | ||
|
||
@Test | ||
fun `should return user message name`() { | ||
val userMessage = Message.user("message") | ||
assertEquals(Role.User, userMessage.role) | ||
assertEquals("message", userMessage.content) | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
...hropic-client-core/src/jvmTest/kotlin/com/tddworks/anthropic/api/messages/api/RoleTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.tddworks.anthropic.api.messages.api | ||
|
||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
|
||
|
||
class RoleTest { | ||
|
||
@Test | ||
fun `should return correct role name`() { | ||
assertEquals("user", Role.User.name) | ||
assertEquals("assistant", Role.Assistant.name) | ||
} | ||
} |
Oops, something went wrong.