Skip to content

Commit

Permalink
feat(BE-174): integrate anthropic api
Browse files Browse the repository at this point in the history
 - code refactor.
 - make create api working.
  • Loading branch information
hanrw committed Mar 16, 2024
1 parent 4500368 commit b95c509
Show file tree
Hide file tree
Showing 20 changed files with 250 additions and 157 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.tddworks.anthropic.api

import com.tddworks.anthropic.api.Anthropic.Companion.BASE_URL
import com.tddworks.anthropic.api.messages.api.AnthropicConfig
import com.tddworks.anthropic.api.messages.api.Messages
import com.tddworks.anthropic.api.messages.api.internal.DefaultMessagesApi
import com.tddworks.common.network.api.ktor.api.HttpRequester
import com.tddworks.common.network.api.ktor.internal.createHttpClient
import com.tddworks.common.network.api.ktor.internal.default
import io.ktor.client.engine.*

interface Anthropic : Messages {
companion object {
const val BASE_URL = "klaude.asusual.life"
}
}

fun Anthropic(apiKey: String, engine: HttpClientEngineFactory<HttpClientEngineConfig>): Anthropic = AnthropicApi(
apiKey = apiKey,
HttpRequester.default(
createHttpClient(
url = BASE_URL, engine = engine
)
),
)

class AnthropicApi(
private val apiKey: String,
private val requester: HttpRequester,
) : Anthropic, Messages by DefaultMessagesApi(
AnthropicConfig(
apiKey = apiKey
),
requester
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tddworks.anthropic.api.messages.api

data class AnthropicConfig(
val apiKey: String = "CONFIG_API_KEY",
val anthropicVersion: String = "2023-06-01",
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package com.tddworks.anthropic.api.messages.api

import com.tddworks.common.network.api.StreamableRequest
import com.tddworks.openllm.api.ChatRequest
import com.tddworks.anthropic.api.Model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
@SerialName("CreateMessageRequest")
data class CreateMessageRequest(
val messages: List<Message>,
val systemPrompt: String? = null,
) : ChatRequest, StreamableRequest {
@SerialName("max_tokens")
val maxTokens: Int = 1024,
@SerialName("model")
val model: Model = Model.CLAUDE_3_HAIKU,
) : StreamMessageRequest {
companion object {
fun streamRequest(messages: List<Message>, systemPrompt: String? = null) =
CreateMessageRequest(messages, systemPrompt) as StreamableRequest
CreateMessageRequest(messages, systemPrompt) as StreamMessageRequest
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.tddworks.anthropic.api.messages.api

import kotlinx.coroutines.flow.Flow

/**
* * Anthropic Messages API -https://docs.anthropic.com/claude/reference/messages_post
*/
interface Messages {
suspend fun create(request: CreateMessageRequest): CreateMessageResponse

fun stream(request: StreamMessageRequest): Flow<StreamMessageResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tddworks.anthropic.api.messages.api

import com.tddworks.common.network.api.StreamableRequest

interface StreamMessageRequest : StreamableRequest
Original file line number Diff line number Diff line change
@@ -1,67 +1,63 @@
package com.tddworks.anthropic.api.messages.api.stream
package com.tddworks.anthropic.api.messages.api

import com.tddworks.anthropic.api.messages.api.CreateMessageResponse
import com.tddworks.anthropic.api.messages.api.Usage
import com.tddworks.common.network.api.StreamChatResponse
import com.tddworks.anthropic.api.messages.api.internal.json.StreamMessageResponseSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@SerialName("message_start")

@Serializable(with = StreamMessageResponseSerializer::class)
sealed interface StreamMessageResponse {
val type: String
}

@Serializable
data class MessageStart(
override val type: String,
val message: CreateMessageResponse,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("content_block_start")
data class ContentBlockStart(
override val type: String,
val index: Int,
@SerialName("content_block")
val contentBlock: ContentBlock,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("content_block_delta")
data class ContentBlock(
override val type: String,
val text: String,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("content_block_delta")
data class ContentBlockDelta(
override val type: String,
val index: Int,
val delta: Delta,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("content_block_stop")
data class ContentBlockStop(
override val type: String,
val index: Int,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("message_delta")
data class MessageDelta(
override val type: String,
val delta: Delta,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("message_stop")
data class MessageStop(
override val type: String,
) : StreamChatResponse
) : StreamMessageResponse

@Serializable
@SerialName("ping")
data class Ping(
override val type: String,
) : StreamChatResponse
) : StreamMessageResponse

/**
* {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
package com.tddworks.anthropic.api.messages.api.internal

import com.tddworks.common.network.api.StreamChatResponse
import com.tddworks.common.network.api.StreamableRequest
import com.tddworks.anthropic.api.messages.api.*
import com.tddworks.common.network.api.ktor.api.HttpRequester
import com.tddworks.common.network.api.ktor.api.performRequest
import com.tddworks.common.network.api.ktor.api.streamRequest
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.*
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.json.Json

/**
* * Anthropic Messages API -https://docs.anthropic.com/claude/reference/messages_post
* * Anthropic Messages API - https://docs.anthropic.com/claude/reference/messages_post
*/
class DefaultMessagesApi(
private val anthropicConfig: AnthropicConfig = AnthropicConfig(),
private val requester: HttpRequester,
private val jsonLenient: Json = JsonLenient,
) : ChatApi {
) : Messages {

override fun chat(request: StreamableRequest): Flow<StreamChatResponse> {
return requester.streamRequest<StreamChatResponse> {
override fun stream(request: StreamMessageRequest): Flow<StreamMessageResponse> {
return requester.streamRequest<StreamMessageResponse> {
method = HttpMethod.Post
url(path = CHAT_COMPLETIONS_PATH)
setBody(request.asStreamRequest(jsonLenient))
Expand All @@ -31,6 +28,8 @@ class DefaultMessagesApi(
headers {
append(HttpHeaders.CacheControl, "no-cache")
append(HttpHeaders.Connection, "keep-alive")
append("anthropic-version", anthropicConfig.anthropicVersion)
append("x-api-key", anthropicConfig.apiKey)
}
}
}
Expand All @@ -40,12 +39,17 @@ class DefaultMessagesApi(
* @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> {
override suspend fun create(request: CreateMessageRequest): CreateMessageResponse {
return requester.performRequest<CreateMessageResponse> {
method = HttpMethod.Post
url(path = CHAT_COMPLETIONS_PATH)
setBody(request)
contentType(ContentType.Application.Json)
// anthropic API uses API key and
headers {
append("anthropic-version", anthropicConfig.anthropicVersion)
append("x-api-key", anthropicConfig.apiKey)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tddworks.anthropic.api.messages.api.internal

import com.tddworks.anthropic.api.messages.api.internal.json.chatModule
import com.tddworks.anthropic.api.messages.api.internal.json.anthropicModule
import kotlinx.serialization.json.Json


Expand All @@ -17,5 +17,5 @@ val JsonLenient = Json {
ignoreUnknownKeys = true
// https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#class-discriminator-for-polymorphism
classDiscriminator = "#class"
serializersModule = chatModule
serializersModule = anthropicModule
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tddworks.anthropic.api.messages.api.internal.json

import com.tddworks.anthropic.api.messages.api.*
import com.tddworks.common.network.api.StreamableRequest
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic

val anthropicModule = SerializersModule {

polymorphic(StreamableRequest::class) {
subclass(CreateMessageRequest::class, CreateMessageRequest.serializer())
defaultDeserializer { CreateMessageRequest.serializer() }
}
}

object StreamMessageResponseSerializer :
JsonContentPolymorphicSerializer<StreamMessageResponse>(StreamMessageResponse::class) {
override fun selectDeserializer(element: JsonElement): KSerializer<out StreamMessageResponse> {
val type = element.jsonObject["type"]?.jsonPrimitive?.content

return when (type) {
"message_start" -> MessageStart.serializer()
"content_block_start" -> ContentBlockStart.serializer()
"content_block_delta" -> ContentBlockDelta.serializer()
"content_block_stop" -> ContentBlockStop.serializer()
"message_delta" -> MessageDelta.serializer()
"message_stop" -> MessageStop.serializer()
"ping" -> Ping.serializer()
else -> throw IllegalArgumentException("Unknown type of message")
}
}
}

This file was deleted.

Loading

0 comments on commit b95c509

Please sign in to comment.