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/commonMain/kotlin/tool/computer/Computer.kt b/src/commonMain/kotlin/tool/computer/Computer.kt new file mode 100644 index 0000000..3c7d973 --- /dev/null +++ b/src/commonMain/kotlin/tool/computer/Computer.kt @@ -0,0 +1,33 @@ +package com.xemantic.anthropic.tool.computer + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +enum class Action { + @SerialName("key") + KEY, + @SerialName("type") + TYPE, + @SerialName("mouse_move") + MOUSE_MOVE, + @SerialName("left_click") + LEFT_CLICK, + @SerialName("left_click_drag") + LEFT_CLICK_DRAG, + @SerialName("right_click") + RIGHT_CLICK, + @SerialName("middle_click") + MIDDLE_CLICK, + @SerialName("double_click") + DOUBLE_CLICK, + @SerialName("screenshot") + SCREENSHOT, + @SerialName("cursor_position") + CURSOR_POSITION, +} + +@Serializable +data class Resolution( + val width: Int, + val height: Int +) diff --git a/src/commonTest/kotlin/AnthropicTest.kt b/src/commonTest/kotlin/AnthropicTest.kt index b152c9f..94beddf 100644 --- a/src/commonTest/kotlin/AnthropicTest.kt +++ b/src/commonTest/kotlin/AnthropicTest.kt @@ -45,7 +45,7 @@ class AnthropicTest { assertSoftly(response) { type shouldBe "message" role shouldBe Role.ASSISTANT - model shouldBe "claude-3-5-sonnet-20240620" + model shouldBe "claude-3-5-sonnet-20241022" stopReason shouldBe StopReason.END_TURN content.size shouldBe 1 content[0] shouldBe instanceOf() 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() } diff --git a/src/commonTest/kotlin/message/MessageResponseTest.kt b/src/commonTest/kotlin/message/MessageResponseTest.kt index c1566e7..638f8bd 100644 --- a/src/commonTest/kotlin/message/MessageResponseTest.kt +++ b/src/commonTest/kotlin/message/MessageResponseTest.kt @@ -20,7 +20,7 @@ class MessageResponseTest { "id": "msg_01PspkNzNG3nrf5upeTsmWLF", "type": "message", "role": "assistant", - "model": "claude-3-5-sonnet-20240620", + "model": "claude-3-5-sonnet-20241022", "content": [ { "type": "tool_use", @@ -47,7 +47,7 @@ class MessageResponseTest { id shouldBe "msg_01PspkNzNG3nrf5upeTsmWLF" type shouldBe "message" role shouldBe Role.ASSISTANT - model shouldBe "claude-3-5-sonnet-20240620" + model shouldBe "claude-3-5-sonnet-20241022" content.size shouldBe 1 content[0] shouldBe instanceOf() stopReason shouldBe StopReason.TOOL_USE