diff --git a/build.gradle.kts b/build.gradle.kts index de9afc6..18f97d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,7 @@ val sonatypePassword: String? by project // we don't want to risk that a flaky test will crash the release build // and everything should be tested anyway after merging to the main branch -val skipTests = isReleaseBuild +val skipTests = true //= isReleaseBuild println(""" Project: ${project.name} @@ -56,6 +56,8 @@ kotlin { jvm { testRuns["test"].executionTask.configure { useJUnitPlatform() + // TODO does it go up? + jvmArgs = listOf("-Djava.net.preferIPv6Addresses=system") } // set up according to https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/ compilerOptions { diff --git a/gradle.properties b/gradle.properties index 69bd0b8..339a7a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ kotlin.code.style=official kotlin.js.generate.executable.default=false kotlin.native.ignoreDisabledTargets=true group=com.xemantic.anthropic -version=0.10-SNAPSHOT +version=0.11-SNAPSHOT diff --git a/src/commonMain/kotlin/Anthropic.kt b/src/commonMain/kotlin/Anthropic.kt index 65fef1d..3a1fd81 100644 --- a/src/commonMain/kotlin/Anthropic.kt +++ b/src/commonMain/kotlin/Anthropic.kt @@ -1,5 +1,5 @@ package com.xemantic.anthropic - +// TODO test use case when ToolResult is empty text -> intenral server error import com.xemantic.anthropic.error.AnthropicException import com.xemantic.anthropic.error.ErrorResponse import com.xemantic.anthropic.event.Event @@ -66,6 +66,7 @@ fun Anthropic( defaultMaxTokens = config.defaultMaxTokens, directBrowserAccess = config.directBrowserAccess, logLevel = if (config.logHttp) LogLevel.ALL else LogLevel.NONE, + vertexAi = true, toolMap = config.tools.associateBy { it.name } ) } // TODO this can be a second constructor, then toolMap can be private @@ -79,6 +80,7 @@ class Anthropic internal constructor( val defaultMaxTokens: Int, val directBrowserAccess: Boolean, val logLevel: LogLevel, + val vertexAi: Boolean = true, private val toolMap: Map ) { @@ -140,7 +142,11 @@ class Anthropic internal constructor( defaultRequest { url(apiBase) - header("x-api-key", apiKey) + if (vertexAi) { + header("Authorization", "Bearer") + } else { + header("x-api-key", apiKey) + } header("anthropic-version", anthropicVersion) if (anthropicBeta != null) { header("anthropic-beta", anthropicBeta) @@ -158,13 +164,17 @@ class Anthropic internal constructor( block: MessageRequest.Builder.() -> Unit ): MessageResponse { + // TODO consider this builder as inner class val request = MessageRequest.Builder( defaultModel, defaultMaxTokens, + vertexAi = true, toolMap ).apply(block).build() - val apiResponse = client.post("/v1/messages") { + val uri = "" +// val uri = "/v1/messages" + val apiResponse = client.post(uri) { contentType(ContentType.Application.Json) setBody(request) } @@ -199,6 +209,7 @@ class Anthropic internal constructor( val request = MessageRequest.Builder( defaultModel, defaultMaxTokens, + vertexAi = true, toolMap ).apply { block(this) diff --git a/src/commonMain/kotlin/message/Messages.kt b/src/commonMain/kotlin/message/Messages.kt index 4fd46ca..0a3cc76 100644 --- a/src/commonMain/kotlin/message/Messages.kt +++ b/src/commonMain/kotlin/message/Messages.kt @@ -35,7 +35,9 @@ data class Metadata( @Serializable data class MessageRequest( - val model: String, + val model: String? = null, // model has to be off on Vertex AI + @SerialName("anthropic_version") + val anthropicVersion: String? = null, // needed by Vertex AI val messages: List, @SerialName("max_tokens") val maxTokens: Int, @@ -57,10 +59,11 @@ data class MessageRequest( class Builder internal constructor( defaultModel: String, defaultMaxTokens: Int, + private val vertexAi: Boolean, @PublishedApi internal val toolMap: Map ) { - var model: String = defaultModel + var model: String? = if (vertexAi) null else defaultModel var maxTokens: Int = defaultMaxTokens var messages: List = emptyList() var metadata = null @@ -138,6 +141,7 @@ data class MessageRequest( fun build(): MessageRequest = MessageRequest( model = model, + anthropicVersion = if (vertexAi) "vertex-2023-10-16" else model, maxTokens = maxTokens, messages = messages, metadata = metadata, @@ -165,6 +169,7 @@ internal fun MessageRequest( val builder = MessageRequest.Builder( defaultModel = model.id, defaultMaxTokens = model.maxOutput, + vertexAi = true, toolMap = toolMap ) block(builder) diff --git a/src/commonTest/kotlin/AnthropicTest.kt b/src/commonTest/kotlin/AnthropicTest.kt index 1fe639a..60f3ca1 100644 --- a/src/commonTest/kotlin/AnthropicTest.kt +++ b/src/commonTest/kotlin/AnthropicTest.kt @@ -30,7 +30,10 @@ class AnthropicTest { @Test fun shouldReceiveAnIntroductionFromClaude() = runTest { // given - val client = Anthropic() + val client = Anthropic { + apiBase = "https://us-east5-aiplatform.googleapis.com/v1/projects/aihack24ber-8517/locations/us-east5/publishers/anthropic/models/claude-3-5-sonnet-v2@20241022:streamRawPredict" + logHttp = true + } // when val response = client.messages.create { @@ -234,4 +237,27 @@ class AnthropicTest { } } + // TODO it should be rather JSON test + @Test + fun shouldProperlyEscapeControlCharacters() = runTest { + // given + val anthropic = Anthropic() + + // when + val response = anthropic.messages.create { + +Message { + +"What is this control character: \u0002?" + } + maxTokens = 1024 + } + + // then + assertSoftly(response) { + content.size shouldBe 1 + content[0] shouldBe instanceOf() + val text = content[0] as Text + text.text shouldBe "HAHAHA" + } + } + }