From 58d89588ef3e6d9a1f53fca12dd02d6c4773595c Mon Sep 17 00:00:00 2001 From: Kazik Pogoda Date: Fri, 25 Oct 2024 17:56:24 +0200 Subject: [PATCH] initial computer use support according to the latest API changes, model represented as a separate enum --- src/commonMain/kotlin/Anthropic.kt | 20 ++-- src/commonMain/kotlin/Models.kt | 97 +++++++++++++++++++ src/commonMain/kotlin/message/Messages.kt | 17 +++- .../kotlin/message/MessageRequestTest.kt | 8 +- 4 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 src/commonMain/kotlin/Models.kt diff --git a/src/commonMain/kotlin/Anthropic.kt b/src/commonMain/kotlin/Anthropic.kt index 296935d..b56150f 100644 --- a/src/commonMain/kotlin/Anthropic.kt +++ b/src/commonMain/kotlin/Anthropic.kt @@ -42,11 +42,6 @@ const val ANTHROPIC_API_BASE: String = "https://api.anthropic.com/" */ const val DEFAULT_ANTHROPIC_VERSION: String = "2023-06-01" -/** - * The default model to be used if no model is specified. - */ -const val DEFAULT_MODEL = "claude-3-5-sonnet-20240620" - /** * An exception thrown when API requests returns error. */ @@ -79,13 +74,13 @@ fun Anthropic( val config = Anthropic.Config().apply(block) val apiKey = if (config.apiKey != null) config.apiKey else envApiKey requireNotNull(apiKey) { missingApiKeyMessage } - val defaultModel = if (config.defaultModel != null) config.defaultModel!! else DEFAULT_MODEL return Anthropic( apiKey = apiKey, anthropicVersion = config.anthropicVersion, anthropicBeta = config.anthropicBeta, apiBase = config.apiBase, - defaultModel = defaultModel, + defaultModel = config.defaultModel.id, + defaultMaxTokens = config.defaultMaxTokens, directBrowserAccess = config.directBrowserAccess, logLevel = if (config.logHttp) LogLevel.ALL else LogLevel.NONE ).apply { @@ -99,6 +94,7 @@ class Anthropic internal constructor( val anthropicBeta: String?, val apiBase: String, val defaultModel: String, + val defaultMaxTokens: Int, val directBrowserAccess: Boolean, val logLevel: LogLevel ) { @@ -108,7 +104,9 @@ class Anthropic internal constructor( var anthropicVersion: String = DEFAULT_ANTHROPIC_VERSION var anthropicBeta: String? = null var apiBase: String = ANTHROPIC_API_BASE - var defaultModel: String? = null + var defaultModel: Model = Model.DEFAULT + var defaultMaxTokens: Int = defaultModel.maxOutput + var directBrowserAccess: Boolean = false var logHttp: Boolean = false @@ -180,7 +178,8 @@ class Anthropic internal constructor( val request = MessageRequest.Builder( defaultModel, - toolEntryMap = toolEntryMap + defaultMaxTokens, + toolEntryMap ).apply(block).build() val apiResponse = client.post("/v1/messages") { @@ -211,7 +210,8 @@ class Anthropic internal constructor( val request = MessageRequest.Builder( defaultModel, - toolEntryMap = toolEntryMap + defaultMaxTokens, + toolEntryMap ).apply { block(this) stream = true diff --git a/src/commonMain/kotlin/Models.kt b/src/commonMain/kotlin/Models.kt new file mode 100644 index 0000000..4aa9c8b --- /dev/null +++ b/src/commonMain/kotlin/Models.kt @@ -0,0 +1,97 @@ +package com.xemantic.anthropic + +enum class Model( + val id: String, + val contextWindow: Int, + val maxOutput: Int, + val messageBatchesApi: Boolean, + val cost: Cost +) { + + CLAUDE_3_5_SONNET( + id = "claude-3-5-sonnet-latest", + contextWindow = 200000, + maxOutput = 8182, + messageBatchesApi = true, + cost = Cost( + input = 3.0, + output = 15.0 + ) + ), + + CLAUDE_3_5_SONNET_20241022( + id = "claude-3-5-sonnet-20241022", + contextWindow = 200000, + maxOutput = 8182, + messageBatchesApi = true, + cost = Cost( + input = 3.0, + output = 15.0 + ) + ), + + CLAUDE_3_5_SONNET_20240620( + id = "claude-3-5-sonnet-20240620", + contextWindow = 200000, + maxOutput = 8182, + messageBatchesApi = true, + cost = Cost( + input = 3.0, + output = 15.0 + ) + ), + + CLAUDE_3_OPUS( + id = "claude-3-opus-latest", + contextWindow = 200000, + maxOutput = 4096, + messageBatchesApi = true, + cost = Cost( + input = 15.0, + output = 75.0 + ) + ), + + CLAUDE_3_OPUS_20240229( + id = "claude-3-opus-20240229", + contextWindow = 200000, + maxOutput = 4096, + messageBatchesApi = true, + cost = Cost( + input = 15.0, + output = 75.0 + ) + ), + + CLAUDE_3_SONNET_20240229( + id = "claude-3-sonnet-20240229", + contextWindow = 200000, + maxOutput = 4096, + messageBatchesApi = true, + cost = Cost( + input = 3.0, + output = 15.0 + ) + ), + + CLAUDE_3_HAIKU_20240307( + id = "claude-3-haiku-20240307", + contextWindow = 200000, + maxOutput = 4096, + messageBatchesApi = true, + cost = Cost( + input = .25, + output = 1.25 + ) + ); + + /** + * Cost per MTok + */ + data class Cost(val input: Double, val output: Double) + + companion object { + val DEFAULT: Model = CLAUDE_3_5_SONNET + } + +} diff --git a/src/commonMain/kotlin/message/Messages.kt b/src/commonMain/kotlin/message/Messages.kt index f654d45..5756568 100644 --- a/src/commonMain/kotlin/message/Messages.kt +++ b/src/commonMain/kotlin/message/Messages.kt @@ -2,6 +2,7 @@ package com.xemantic.anthropic.message import com.xemantic.anthropic.Anthropic import com.xemantic.anthropic.MessageResponse +import com.xemantic.anthropic.Model import com.xemantic.anthropic.anthropicJson import com.xemantic.anthropic.schema.JsonSchema import com.xemantic.anthropic.tool.UsableTool @@ -53,14 +54,15 @@ data class MessageRequest( class Builder internal constructor( private val defaultModel: String, + private val defaultMaxTokens: Int, @PublishedApi internal val toolEntryMap: Map> ) { var model: String? = null - var maxTokens = 1024 - var messages: List = mutableListOf() + var maxTokens: Int = defaultMaxTokens + var messages: List = emptyList() var metadata = null - val stopSequences = mutableListOf() + var stopSequences: List = emptyList() var stream: Boolean? = null internal set var system: List? = null @@ -128,12 +130,17 @@ data class MessageRequest( } +/** + * Used only in tests. Maybe should be internal? + */ fun MessageRequest( - defaultModel: String, + model: Model = Model.DEFAULT, block: MessageRequest.Builder.() -> Unit ): MessageRequest { val builder = MessageRequest.Builder( - defaultModel, emptyMap() + defaultModel = model.id, + defaultMaxTokens = model.maxOutput, + toolEntryMap = emptyMap() ) block(builder) return builder.build() diff --git a/src/commonTest/kotlin/message/MessageRequestTest.kt b/src/commonTest/kotlin/message/MessageRequestTest.kt index a9244f7..ecce656 100644 --- a/src/commonTest/kotlin/message/MessageRequestTest.kt +++ b/src/commonTest/kotlin/message/MessageRequestTest.kt @@ -22,9 +22,7 @@ class MessageRequestTest { @Test fun shouldCreateTheSimplestMessageRequest() { // given - val request = MessageRequest( - defaultModel = "claude-3-5-sonnet-20240620" - ) { + val request = MessageRequest { +Message { +"Hey Claude!?" } @@ -36,7 +34,7 @@ class MessageRequestTest { // then json shouldEqualJson """ { - "model": "claude-3-5-sonnet-20240620", + "model": "claude-3-5-sonnet-latest", "messages": [ { "role": "user", @@ -48,7 +46,7 @@ class MessageRequestTest { ] } ], - "max_tokens": 1024 + "max_tokens": 8182 } """.trimIndent() }