Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics with OpenTelemetry #481

Merged
merged 7 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.xebia.functional.xef.AIError
import com.xebia.functional.xef.llm.*
import com.xebia.functional.xef.llm.models.functions.CFunction
import com.xebia.functional.xef.llm.models.images.ImagesGenerationResponse
import com.xebia.functional.xef.metrics.Metric
import com.xebia.functional.xef.prompt.Prompt
import com.xebia.functional.xef.store.ConversationId
import com.xebia.functional.xef.store.VectorStore
Expand All @@ -17,6 +18,8 @@ interface Conversation : AutoClose, AutoCloseable {

val store: VectorStore

val metric: Metric

val conversationId: ConversationId?

val conversation: Conversation
Expand Down Expand Up @@ -80,14 +83,16 @@ interface Conversation : AutoClose, AutoCloseable {

operator fun invoke(
store: VectorStore,
metric: Metric,
conversationId: ConversationId? = ConversationId(UUID.generateUUID().toString())
): PlatformConversation = PlatformConversation.create(store, conversationId)
): PlatformConversation = PlatformConversation.create(store, metric, conversationId)

@JvmSynthetic
suspend operator fun <A> invoke(
store: VectorStore,
metric: Metric,
conversationId: ConversationId? = ConversationId(UUID.generateUUID().toString()),
block: suspend PlatformConversation.() -> A
): A = block(invoke(store, conversationId))
): A = block(invoke(store, metric, conversationId))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.xebia.functional.xef.conversation

import com.xebia.functional.xef.metrics.Metric
import com.xebia.functional.xef.store.ConversationId
import com.xebia.functional.xef.store.VectorStore

Expand All @@ -11,6 +12,7 @@ expect abstract class PlatformConversation(
companion object {
fun create(
store: VectorStore,
metric: Metric,
conversationId: ConversationId?,
): PlatformConversation
}
Expand Down
35 changes: 19 additions & 16 deletions core/src/commonMain/kotlin/com/xebia/functional/xef/llm/Chat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,25 @@ interface Chat : LLM {
promptMessages(prompt, scope).firstOrNull() ?: throw AIError.NoResponse()

@AiDsl
suspend fun promptMessages(prompt: Prompt, scope: Conversation): List<String> {
val promptMemories = prompt.messages.toMemory(scope)
val adaptedPrompt = PromptCalculator.adaptPromptToConversationAndModel(prompt, scope, this@Chat)
suspend fun promptMessages(prompt: Prompt, scope: Conversation): List<String> =
scope.metric.promptSpan(scope, prompt) {
val promptMemories = prompt.messages.toMemory(scope)
val adaptedPrompt =
PromptCalculator.adaptPromptToConversationAndModel(prompt, scope, this@Chat)

val request =
ChatCompletionRequest(
user = adaptedPrompt.configuration.user,
messages = adaptedPrompt.messages,
n = adaptedPrompt.configuration.numberOfPredictions,
temperature = adaptedPrompt.configuration.temperature,
maxTokens = adaptedPrompt.configuration.minResponseTokens,
)
val request =
ChatCompletionRequest(
user = adaptedPrompt.configuration.user,
messages = adaptedPrompt.messages,
n = adaptedPrompt.configuration.numberOfPredictions,
temperature = adaptedPrompt.configuration.temperature,
maxTokens = adaptedPrompt.configuration.minResponseTokens,
)

return createChatCompletion(request)
.choices
.addChoiceToMemory(scope, promptMemories)
.mapNotNull { it.message?.content }
}
createChatCompletion(request)
.addMetrics(scope)
.choices
.addChoiceToMemory(scope, promptMemories)
.mapNotNull { it.message?.content }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,36 @@ interface ChatWithFunctions : LLM {
scope: Conversation,
function: CFunction,
serializer: (json: String) -> A,
): A {
val promptWithFunctions = prompt.copy(function = function)
val adaptedPrompt =
PromptCalculator.adaptPromptToConversationAndModel(
promptWithFunctions,
scope,
this@ChatWithFunctions
)
): A =
scope.metric.promptSpan(scope, prompt) {
val promptWithFunctions = prompt.copy(function = function)
val adaptedPrompt =
PromptCalculator.adaptPromptToConversationAndModel(
promptWithFunctions,
scope,
this@ChatWithFunctions
)

val request =
FunChatCompletionRequest(
user = adaptedPrompt.configuration.user,
messages = adaptedPrompt.messages,
n = adaptedPrompt.configuration.numberOfPredictions,
temperature = adaptedPrompt.configuration.temperature,
maxTokens = adaptedPrompt.configuration.minResponseTokens,
functions = adaptedPrompt.function!!.nel(),
functionCall = mapOf("name" to (adaptedPrompt.function.name)),
)
val request =
FunChatCompletionRequest(
user = adaptedPrompt.configuration.user,
messages = adaptedPrompt.messages,
n = adaptedPrompt.configuration.numberOfPredictions,
temperature = adaptedPrompt.configuration.temperature,
maxTokens = adaptedPrompt.configuration.minResponseTokens,
functions = adaptedPrompt.function!!.nel(),
functionCall = mapOf("name" to (adaptedPrompt.function.name)),
)

return tryDeserialize(
serializer,
promptWithFunctions.configuration.maxDeserializationAttempts
) {
val requestedMemories = prompt.messages.toMemory(scope)
createChatCompletionWithFunctions(request)
.choices
.addChoiceWithFunctionsToMemory(scope, requestedMemories)
.mapNotNull { it.message?.functionCall?.arguments }
tryDeserialize(serializer, promptWithFunctions.configuration.maxDeserializationAttempts) {
val requestedMemories = prompt.messages.toMemory(scope)
createChatCompletionWithFunctions(request)
.addMetrics(scope)
.choices
.addChoiceWithFunctionsToMemory(scope, requestedMemories)
.mapNotNull { it.message?.functionCall?.arguments }
}
}
}

@AiDsl
fun <A> promptStreaming(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ interface Embeddings : LLM {
else
texts
.chunked(chunkSize ?: 400)
.parMap { createEmbeddings(EmbeddingRequest(name, it, requestConfig.user.id)).data }
.parMap {
createEmbeddings(EmbeddingRequest(modelType.name, it, requestConfig.user.id)).data
}
.flatten()

suspend fun embedQuery(text: String, requestConfig: RequestConfig): List<Embedding> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.xebia.functional.xef.llm

import com.xebia.functional.xef.conversation.Conversation
import com.xebia.functional.xef.llm.models.chat.ChatCompletionResponse
import com.xebia.functional.xef.llm.models.chat.ChatCompletionResponseWithFunctions

fun ChatCompletionResponseWithFunctions.addMetrics(
conversation: Conversation
): ChatCompletionResponseWithFunctions {
conversation.metric.log(conversation, "Model: ${`object`}")
conversation.metric.log(
conversation,
"Tokens: ${usage.promptTokens} (prompt) + ${usage.completionTokens} (completion) = ${usage.totalTokens}"
)
return this
}

fun ChatCompletionResponse.addMetrics(conversation: Conversation): ChatCompletionResponse {
conversation.metric.log(conversation, "Model: ${`object`}")
conversation.metric.log(
conversation,
"Tokens: ${usage.promptTokens} (prompt) + ${usage.completionTokens} (completion) = ${usage.totalTokens}"
)
return this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.xebia.functional.xef.metrics

import com.xebia.functional.xef.conversation.Conversation
import com.xebia.functional.xef.prompt.Prompt
import io.ktor.util.date.*

class LogsMetric : Metric {

private val identSize = 4

override suspend fun <A> promptSpan(
conversation: Conversation,
prompt: Prompt,
block: suspend Metric.() -> A
): A {
val milis = getTimeMillis()
val name = prompt.messages.lastOrNull()?.content ?: "empty"
println("Prompt-Span: $name")
val output = block()
println("${writeIdent()}|-- Finished in ${getTimeMillis()-milis} ms")
return output
}

override fun log(conversation: Conversation, message: String) {
println("${writeIdent()}|-- $message".padStart(identSize, ' '))
}

private fun writeIdent(times: Int = 1) = (1..identSize * times).fold("") { a, b -> "$a " }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.xebia.functional.xef.metrics

import com.xebia.functional.xef.conversation.Conversation
import com.xebia.functional.xef.prompt.Prompt

interface Metric {
suspend fun <A> promptSpan(
conversation: Conversation,
prompt: Prompt,
block: suspend Metric.() -> A
): A

fun log(conversation: Conversation, message: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.xebia.functional.tokenizer.ModelType
import com.xebia.functional.xef.data.*
import com.xebia.functional.xef.llm.models.chat.Message
import com.xebia.functional.xef.llm.models.chat.Role
import com.xebia.functional.xef.metrics.LogsMetric
import com.xebia.functional.xef.prompt.Prompt
import com.xebia.functional.xef.prompt.templates.assistant
import com.xebia.functional.xef.prompt.templates.system
Expand All @@ -26,7 +27,12 @@ class ConversationSpec :

val model = TestModel(modelType = ModelType.ADA)

val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)

val vectorStore = scope.store

Expand All @@ -48,7 +54,12 @@ class ConversationSpec :
|""" {
val messages = generateRandomMessages(50, 40, 60)
val conversationId = ConversationId(UUID.generateUUID().toString())
val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)
val vectorStore = scope.store

val modelAda = TestModel(modelType = ModelType.ADA, responses = messages)
Expand Down Expand Up @@ -85,7 +96,12 @@ class ConversationSpec :
|""" {
val messages = generateRandomMessages(50, 40, 60)
val conversationId = ConversationId(UUID.generateUUID().toString())
val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)
val vectorStore = scope.store

val modelGPTTurbo16K =
Expand Down Expand Up @@ -122,7 +138,12 @@ class ConversationSpec :
val message = mapOf(question to Json.encodeToString(answer))

val conversationId = ConversationId(UUID.generateUUID().toString())
val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)

val model =
TestFunctionsModel(modelType = ModelType.GPT_3_5_TURBO_FUNCTIONS, responses = message)
Expand All @@ -146,7 +167,12 @@ class ConversationSpec :
val message = mapOf(questionJsonString to answerJsonString)

val conversationId = ConversationId(UUID.generateUUID().toString())
val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)

val model =
TestFunctionsModel(modelType = ModelType.GPT_3_5_TURBO_FUNCTIONS, responses = message)
Expand All @@ -170,7 +196,12 @@ class ConversationSpec :

val model = TestModel(modelType = ModelType.ADA)

val scope = Conversation(LocalVectorStore(TestEmbeddings()), conversationId = conversationId)
val scope =
Conversation(
LocalVectorStore(TestEmbeddings()),
LogsMetric(),
conversationId = conversationId
)

val vectorStore = scope.store

Expand Down Expand Up @@ -218,7 +249,7 @@ class ConversationSpec :

val vectorStore = LocalVectorStore(TestEmbeddings())

val scope1 = Conversation(vectorStore, conversationId = conversationId)
val scope1 = Conversation(vectorStore, LogsMetric(), conversationId = conversationId)

val firstPrompt = Prompt {
+user("question in scope 1")
Expand All @@ -227,7 +258,7 @@ class ConversationSpec :

model.promptMessages(prompt = firstPrompt, scope = scope1)

val scope2 = Conversation(vectorStore, conversationId = conversationId)
val scope2 = Conversation(vectorStore, LogsMetric(), conversationId = conversationId)

val secondPrompt = Prompt {
+user("question in scope 2")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.xebia.functional.xef.conversation

import com.xebia.functional.xef.metrics.Metric
import com.xebia.functional.xef.store.ConversationId
import com.xebia.functional.xef.store.VectorStore

class JSConversation(
override val store: VectorStore,
override val metric: Metric,
override val conversationId: ConversationId?
) : PlatformConversation(store, conversationId) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.xebia.functional.xef.conversation

import com.xebia.functional.xef.metrics.Metric
import com.xebia.functional.xef.store.ConversationId
import com.xebia.functional.xef.store.VectorStore

actual abstract class PlatformConversation
actual constructor(store: VectorStore, conversationId: ConversationId?) : Conversation, AutoClose {
actual companion object {
actual fun create(store: VectorStore, conversationId: ConversationId?): PlatformConversation {
actual fun create(
store: VectorStore,
metric: Metric,
conversationId: ConversationId?
): PlatformConversation {
conversationId?.let { store.updateIndexByConversationId(conversationId) }
return JSConversation(store, conversationId)
return JSConversation(store, metric, conversationId)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.xebia.functional.xef.conversation

import com.xebia.functional.xef.metrics.Metric
import com.xebia.functional.xef.store.ConversationId
import com.xebia.functional.xef.store.VectorStore
import java.io.Closeable

open class JVMConversation(
override val store: VectorStore,
override val metric: Metric,
override val conversationId: ConversationId?,
) :
PlatformConversation(store, conversationId), AutoClose by autoClose(), AutoCloseable, Closeable {
Expand Down
Loading