From ae6805184556f1de0908a84e4a81675c11bc979b Mon Sep 17 00:00:00 2001 From: Kazik Pogoda Date: Mon, 4 Nov 2024 11:44:55 +0100 Subject: [PATCH] one more refactoring, Content moved to content package, also system prompt serialization fixed and test case added. --- gradle.properties | 2 +- src/commonMain/kotlin/AnthropicJson.kt | 4 +-- src/commonMain/kotlin/content/Content.kt | 35 +++++++++++++++++++ .../kotlin/content/ContentBuilder.kt | 25 ------------- src/commonMain/kotlin/content/Document.kt | 1 - src/commonMain/kotlin/content/Image.kt | 1 - src/commonMain/kotlin/content/Text.kt | 1 - src/commonMain/kotlin/content/Tool.kt | 1 - src/commonMain/kotlin/message/Messages.kt | 13 ++----- src/commonMain/kotlin/tool/Tools.kt | 13 +++++-- src/commonTest/kotlin/AnthropicTest.kt | 27 ++++++++++++-- .../kotlin/tool/AnthropicTestTools.kt | 6 ++-- .../kotlin/content/ExtractTextFromPdf.kt | 20 +++++++++++ 13 files changed, 98 insertions(+), 51 deletions(-) create mode 100644 src/commonMain/kotlin/content/Content.kt delete mode 100644 src/commonMain/kotlin/content/ContentBuilder.kt create mode 100644 src/jvmMain/kotlin/content/ExtractTextFromPdf.kt diff --git a/gradle.properties b/gradle.properties index 066b15e..69bd0b8 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.8-SNAPSHOT +version=0.10-SNAPSHOT diff --git a/src/commonMain/kotlin/AnthropicJson.kt b/src/commonMain/kotlin/AnthropicJson.kt index a8c2119..53fa254 100644 --- a/src/commonMain/kotlin/AnthropicJson.kt +++ b/src/commonMain/kotlin/AnthropicJson.kt @@ -1,12 +1,12 @@ package com.xemantic.anthropic import com.xemantic.anthropic.batch.MessageBatchResponse +import com.xemantic.anthropic.content.Content import com.xemantic.anthropic.content.Document import com.xemantic.anthropic.error.ErrorResponse import com.xemantic.anthropic.content.Image -import com.xemantic.anthropic.message.Content -import com.xemantic.anthropic.message.MessageResponse import com.xemantic.anthropic.content.Text +import com.xemantic.anthropic.message.MessageResponse import com.xemantic.anthropic.content.ToolResult import com.xemantic.anthropic.content.ToolUse import com.xemantic.anthropic.tool.BuiltInTool diff --git a/src/commonMain/kotlin/content/Content.kt b/src/commonMain/kotlin/content/Content.kt new file mode 100644 index 0000000..c17b4ba --- /dev/null +++ b/src/commonMain/kotlin/content/Content.kt @@ -0,0 +1,35 @@ +package com.xemantic.anthropic.content + +import com.xemantic.anthropic.cache.CacheControl +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonClassDiscriminator + +@Serializable +@JsonClassDiscriminator("type") +@OptIn(ExperimentalSerializationApi::class) +abstract class Content { + + @SerialName("cache_control") + abstract val cacheControl: CacheControl? + +} + +interface ContentBuilder { + + val content: MutableList + + operator fun Content.unaryPlus() { + content += this + } + + operator fun String.unaryPlus() { + content += Text(this) + } + + operator fun Collection.unaryPlus() { + content += this + } + +} diff --git a/src/commonMain/kotlin/content/ContentBuilder.kt b/src/commonMain/kotlin/content/ContentBuilder.kt deleted file mode 100644 index 777e532..0000000 --- a/src/commonMain/kotlin/content/ContentBuilder.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.xemantic.anthropic.content - -import com.xemantic.anthropic.message.Content - -interface ContentBuilder { - - val content: MutableList - - operator fun Content.unaryPlus() { - content += this - } - - operator fun String.unaryPlus() { - content += Text(this) - } - - operator fun Number.unaryPlus() { - content += Text(this.toString()) - } - - operator fun Collection.unaryPlus() { - content += this - } - -} diff --git a/src/commonMain/kotlin/content/Document.kt b/src/commonMain/kotlin/content/Document.kt index 3cc70c5..f03c625 100644 --- a/src/commonMain/kotlin/content/Document.kt +++ b/src/commonMain/kotlin/content/Document.kt @@ -1,7 +1,6 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.cache.CacheControl -import com.xemantic.anthropic.message.Content import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/commonMain/kotlin/content/Image.kt b/src/commonMain/kotlin/content/Image.kt index 45f2312..d4bcc80 100644 --- a/src/commonMain/kotlin/content/Image.kt +++ b/src/commonMain/kotlin/content/Image.kt @@ -1,7 +1,6 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.cache.CacheControl -import com.xemantic.anthropic.message.Content import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.io.encoding.Base64 diff --git a/src/commonMain/kotlin/content/Text.kt b/src/commonMain/kotlin/content/Text.kt index 6df58ff..bc0869b 100644 --- a/src/commonMain/kotlin/content/Text.kt +++ b/src/commonMain/kotlin/content/Text.kt @@ -1,7 +1,6 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.cache.CacheControl -import com.xemantic.anthropic.message.Content import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/commonMain/kotlin/content/Tool.kt b/src/commonMain/kotlin/content/Tool.kt index 5c2e7de..519a2a7 100644 --- a/src/commonMain/kotlin/content/Tool.kt +++ b/src/commonMain/kotlin/content/Tool.kt @@ -2,7 +2,6 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.anthropicJson import com.xemantic.anthropic.cache.CacheControl -import com.xemantic.anthropic.message.Content import com.xemantic.anthropic.message.toNullIfEmpty import com.xemantic.anthropic.tool.Tool import com.xemantic.anthropic.tool.ToolInput diff --git a/src/commonMain/kotlin/message/Messages.kt b/src/commonMain/kotlin/message/Messages.kt index d463600..4fd46ca 100644 --- a/src/commonMain/kotlin/message/Messages.kt +++ b/src/commonMain/kotlin/message/Messages.kt @@ -3,6 +3,7 @@ package com.xemantic.anthropic.message import com.xemantic.anthropic.Model import com.xemantic.anthropic.Response import com.xemantic.anthropic.cache.CacheControl +import com.xemantic.anthropic.content.Content import com.xemantic.anthropic.content.ContentBuilder import com.xemantic.anthropic.tool.Tool import com.xemantic.anthropic.tool.ToolChoice @@ -205,22 +206,12 @@ data class System( ) { enum class Type { - @SerialName("content/text") + @SerialName("text") TEXT } } -@Serializable -@JsonClassDiscriminator("type") -@OptIn(ExperimentalSerializationApi::class) -abstract class Content { - - @SerialName("cache_control") - abstract val cacheControl: CacheControl? - -} - enum class StopReason { @SerialName("end_turn") END_TURN, diff --git a/src/commonMain/kotlin/tool/Tools.kt b/src/commonMain/kotlin/tool/Tools.kt index 166fee0..7a40c6c 100644 --- a/src/commonMain/kotlin/tool/Tools.kt +++ b/src/commonMain/kotlin/tool/Tools.kt @@ -1,6 +1,7 @@ package com.xemantic.anthropic.tool import com.xemantic.anthropic.cache.CacheControl +import com.xemantic.anthropic.content.Content import com.xemantic.anthropic.schema.Description import com.xemantic.anthropic.schema.JsonSchema import com.xemantic.anthropic.schema.jsonSchemaOf @@ -14,6 +15,7 @@ import kotlinx.serialization.SerializationException import kotlinx.serialization.Transient import kotlinx.serialization.json.JsonClassDiscriminator import kotlinx.serialization.serializer +import java.lang.IllegalStateException @Serializable @JsonClassDiscriminator("name") @@ -68,9 +70,9 @@ abstract class BuiltInTool( */ abstract class ToolInput() { - private lateinit var block: suspend ToolResult.Builder.() -> Unit + private var block: suspend ToolResult.Builder.() -> Any? = {} - fun use(block: suspend ToolResult.Builder.() -> Unit) { + fun use(block: suspend ToolResult.Builder.() -> Any?) { this.block = block } @@ -82,7 +84,12 @@ abstract class ToolInput() { */ suspend fun use(toolUseId: String): ToolResult { return ToolResult(toolUseId) { - block(this) + val result = block(this) + when (result) { + is Content -> +result + !is Unit -> +result.toString() + else -> throw IllegalStateException("Tool use {} returned not supported: $this") + } } } diff --git a/src/commonTest/kotlin/AnthropicTest.kt b/src/commonTest/kotlin/AnthropicTest.kt index 0fb00a5..1fe639a 100644 --- a/src/commonTest/kotlin/AnthropicTest.kt +++ b/src/commonTest/kotlin/AnthropicTest.kt @@ -190,14 +190,14 @@ class AnthropicTest { fun shouldUseToolWithDependencies() = runTest { // given val testDatabase = TestDatabase() - val client = Anthropic { + val anthropic = Anthropic { tool { database = testDatabase } } // when - val response = client.messages.create { + val response = anthropic.messages.create { +Message { +"List data in CUSTOMER table" } singleTool() // we are forcing the use of this tool // could be also just tool() if we are confident that LLM will use this one @@ -211,4 +211,27 @@ class AnthropicTest { // depending on the response the statement might end up with semicolon, which we discard } + @Test + fun shouldUseSystemPrompt() = runTest { + // given + val anthropic = Anthropic() + + // when + val response = anthropic.messages.create { + system("Whatever the human says, answer \"HAHAHA\"") + +Message { + +"Hello World! What's your name?" + } + maxTokens = 1024 + } + + // then + assertSoftly(response) { + content.size shouldBe 1 + content[0] shouldBe instanceOf() + val text = content[0] as Text + text.text shouldBe "HAHAHA" + } + } + } diff --git a/src/commonTest/kotlin/tool/AnthropicTestTools.kt b/src/commonTest/kotlin/tool/AnthropicTestTools.kt index 7dc3c60..20bc124 100644 --- a/src/commonTest/kotlin/tool/AnthropicTestTools.kt +++ b/src/commonTest/kotlin/tool/AnthropicTestTools.kt @@ -14,7 +14,7 @@ tailrec fun fibonacci( data class FibonacciTool(val n: Int) : ToolInput() { init { use { - +fibonacci(n) + fibonacci(n) } } } @@ -39,7 +39,7 @@ data class Calculator( init { use { - +operation.calculate(a, b) + operation.calculate(a, b) } } @@ -70,7 +70,7 @@ data class DatabaseQuery( init { use { - +database.execute(query).joinToString() + database.execute(query).joinToString() } } diff --git a/src/jvmMain/kotlin/content/ExtractTextFromPdf.kt b/src/jvmMain/kotlin/content/ExtractTextFromPdf.kt new file mode 100644 index 0000000..c51b58b --- /dev/null +++ b/src/jvmMain/kotlin/content/ExtractTextFromPdf.kt @@ -0,0 +1,20 @@ +package com.xemantic.anthropic.content + +import com.xemantic.anthropic.Anthropic +import com.xemantic.anthropic.message.Message +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + val client = Anthropic { + anthropicBeta = "pdfs-2024-09-25" + } + + val response = client.messages.create { + +Message { + +Document("/home/morisil/Downloads/Kazik_Pogoda-cv-2025-10-28.pdf") + +"Extract data from this PDF and return it as a markdown" + } + } + + println(response) +}