openai-kotlin

repository·main·Indexed 23 days ago

https://github.com/aallam/openai-kotlin

A Kotlin client for the OpenAI API supporting multiplatform projects and asynchronous operations via Coroutines. It provides implementations for Chat Completions, Tool Calls, and the Assistants API (beta), including support for streaming and tool outputs. The library can be integrated via Gradle (with an optional BOM for version management) or Maven, and requires a Ktor engine for HTTP requests.

Tokens
9.2K
Snippets
26
Records
32
Agent score
83%

What's inside openai-kotlin

  1. Handle tool calls and streaming in Assistants API

    main

    When working with the Assistants API, you may need to handle advanced execution flows:

    • Tool Outputs: If a run enters the Status.RequiresAction state, you must submit the results of any required tool calls using openAI.submitToolOutput(...).
    • Streaming: For real-time event updates during a run, instead of polling, use openAI.createStreamingRun(...) or openAI.createStreamingThreadRun(...) to stream run events.
  2. Use the Assistants API in Kotlin

    main

    The Assistants API allows you to create assistants, manage threads for conversations, and execute runs to generate responses.

    Note: The Assistants, Threads, Messages, and Runs APIs are currently marked as beta in this client.

    Workflow Summary

    1. Configure Client: Initialize OpenAI with your API key.
    2. Create Assistant: Define an assistant using openAI.assistant(AssistantRequest(...)) specifying name, instructions, tools (e.g., AssistantTool.CodeInterpreter), and a ModelId.
    3. Manage Threads: Create a conversation context using openAI.thread(). Each end user should typically have their own thread.
    4. Add Messages: Add user input to a thread using openAI.message(threadId, MessageRequest(...)).
    5. Execute Runs: Start the assistant's processing of the thread using openAI.createRun(threadId, RunRequest(...)). You can provide additional instructions for the run here.
    6. Poll for Completion: Since runs are asynchronous, poll the status using openAI.getRun(threadId, runId) until the status reaches Status.Completed.
    7. Retrieve Responses: Fetch the conversation history using openAI.messages(threadId) and iterate through the content to find MessageContent.Text.
    suspend fun main() {
        // 1) Configure client
        val token = System.getenv("OPENAI_API_KEY")
        val openAI = OpenAI(token)
    
        // 2) Create an assistant
        val assistant = openAI.assistant(
            request = AssistantRequest(
                name = "Math Tutor",
                instructions = "You are a personal math tutor. Write and run code to answer math questions.",
                tools = listOf(AssistantTool.CodeInterpreter),
                model = ModelId("gpt-4o-mini")
            )
        )
    
        // 3) Create a thread
        val thread = openAI.thread()
    
        // 4) Add a user message to the thread
        openAI.message(
            threadId = thread.id,
            request = MessageRequest(
                role = Role.User,
                content = "I need to solve the equation 3x + 11 = 14. Can you help me?"
            )
        )
    
        // 5) Start a run
        val run = openAI.createRun(
            threadId = thread.id,
            request = RunRequest(
                assistantId = assistant.id,
                instructions = "Please address the user as Jane Doe."
            )
        )
    
        // 6) Poll until completed
        var retrievedRun: Run
        do {
            delay(1500)
            retrievedRun = openAI.getRun(threadId = thread.id, runId = run.id)
        } while (retrievedRun.status != Status.Completed)
    
        // 7) Read assistant messages
        val messages = openAI.messages(thread.id)
        println("Assistant response:")
        for (message in messages) {
            val text = message.content.firstOrNull() as? MessageContent.Text ?: continue
            println(text.text.value)
        }
    }
  3. Define function parameters using JSON Schema

    main

    To tell the model how to use a function, you must define its parameters using a JSON Schema object. This is constructed using the Parameters.buildJsonObject DSL. You specify the type (usually object), the properties (the arguments the function accepts), and a required array listing mandatory fields.

    val params = Parameters.buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            putJsonObject("location") {
                put("type", "string")
                put("description", "The city and state, e.g. San Francisco, CA")
            }
            putJsonObject("unit") {
                put("type", "string")
                putJsonArray("enum") {
                    add("celsius")
                    add("fahrenheit")
                }
            }
        }
        putJsonArray("required") {
            add("location")
        }
    }
  4. Manage Vector Stores for file_search

    main

    Vector stores allow you to store and index files for file_search use cases. You can create, list, retrieve, update, and delete vector stores, as well as manage the files attached to them.

    To attach files to a vector store, use createVectorStoreFile. For large numbers of files, use createVectorStoreFilesBatch to perform batch operations.

    // Create a vector store
    val vectorStore = openAI.createVectorStore(
        request = VectorStoreRequest(name = "Support FAQ")
    )
    
    // Attach a file to a vector store
    val vectorStoreFile = openAI.createVectorStoreFile(
        id = VectorStoreId("vs_abc123"),
        request = VectorStoreFileRequest(fileId = FileId("file-abc123"))
    )
    
    // Batch files into a vector store
    val batch = openAI.createVectorStoreFilesBatch(
        id = VectorStoreId("vs_abc123"),
        request = FileBatchRequest(fileIds = listOf(FileId("file-abc123"), FileId("file-def456")))
    )
  5. Install the OpenAI Kotlin client via Maven

    main

    For Maven projects, use the openai-client-jvm artifact. Note that the BOM is not supported in Maven. You must also include a Ktor engine (e.g., ktor-client-okhttp-jvm) with runtime scope.

    <dependencies>
        <dependency>
            <groupId>com.aallam.openai</groupId>
            <artifactId>openai-client-jvm</artifactId>
            <version>4.1.0</version>
        </dependency>
                
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-okhttp-jvm</artifactId>
            <version>3.0.0</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
  6. Implement Chat Completions with Tool Calls

    main

    You can use the OpenAI Kotlin client to perform chat completions that include tool definitions. This allows the model to request function calls when it determines a tool is necessary to answer a prompt.

    Workflow

    1. Define Tools: Use chatCompletionRequest and the tools block to define functions. Parameters should be defined using kotlinx.serialization.json.buildJsonObject to match the JSON Schema format required by OpenAI.
    2. Set Tool Choice: Use toolChoice = ToolChoice.Auto to let the model decide, or specify a specific function using ToolChoice.function("functionName").
    3. Handle Tool Calls: After receiving a response, iterate through message.toolCalls. For each ToolCall.Function, execute your local logic and capture the result.
    4. Submit Tool Results: Append the tool results back to the chatMessages list as ChatRole.Tool messages (including the toolCallId and name) and call chatCompletion again to get the final response from the model.

    Best Practices

    • Validate Arguments: Always validate the JSON arguments provided by the model before executing side effects.
    • User Confirmation: For actions with real-world impact, implement a user confirmation step.
    • Specificity: Keep the list of tools small and highly specific to improve model accuracy.
    import com.aallam.openai.api.chat.*
    import com.aallam.openai.api.model.ModelId
    import com.aallam.openai.client.OpenAI
    import kotlinx.serialization.json.*
    
    // ... setup client and model ...
    
    val params = Parameters.buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            putJsonObject("location") {
                put("type", "string")
                put("description", "The city and state, e.g. San Francisco, CA")
            }
        }
        putJsonArray("required") {
            add("location")
        }
    }
    
    val request = chatCompletionRequest {
        model = ModelId("gpt-4o-mini")
        messages = chatMessages
        tools {
            function(
                name = "currentWeather",
                description = "Get the current weather in a given location",
                parameters = params
            )
        }
        toolChoice = ToolChoice.Auto
    }
    
    val response = openAI.chatCompletion(request)
    val message = response.choices.first().message
    
    // Process tool calls
    for (toolCall in message.toolCalls.orEmpty()) {
        require(toolCall is ToolCall.Function) { "Tool call is not a function" }
        val functionResponse = toolCall.execute() // Custom execution logic
        chatMessages.append(toolCall, functionResponse)
    }
    
    // Get final response
    val secondResponse = openAI.chatCompletion(
        request = ChatCompletionRequest(model = modelId, messages = chatMessages)
    )
  7. Configure custom OpenAI-compatible hosts

    main

    While the default host is https://api.openai.com/v1/, you can connect to Azure or other compatible hosts by providing a custom OpenAIHost to the OpenAIConfig.

    // Azure configuration
    val host = OpenAIHost.azure(
        resourceName = "your-resource-name",
        deploymentId = "your-deployment-id",
        apiVersion = "2024-10-21",
    )
    
    val config = OpenAIConfig(
        host = host,
        token = "your-api-token",
    )
    
    val openAI = OpenAI(config)
    
    // Custom compatible host
    val host = OpenAIHost(
        baseUrl = "http://localhost:8080/v1/",
    )
    
    val config = OpenAIConfig(
        host = host,
        token = "your-api-token",
    )
    
    val openAI = OpenAI(config)
  8. Install the OpenAI Kotlin client using the BOM

    main

    You can use the openai-client-bom to manage versions more easily. When using the BOM, you can define the openai-client dependency without an explicit version, but you still need to provide a Ktor engine at runtime.

    dependencies {
        // import Kotlin API client BOM
        implementation platform('com.aallam.openai:openai-client-bom:4.1.0')
    
        // define dependencies without versions
        implementation 'com.aallam.openai:openai-client'
        runtimeOnly 'io.ktor:ktor-client-okhttp'
    }
  9. Create a FileSource from a custom RawSource

    main

    If you already have a RawSource (for example, from an in-memory stream or a custom implementation) and want to specify the filename that will be sent to the OpenAI API, use the FileSource constructor with the name and source parameters.

    import com.aallam.openai.api.file.FileSource
    import kotlinx.io.files.Path
    import kotlinx.io.files.SystemFileSystem
    
    val source = SystemFileSystem.source(Path("path/to/audio.wav"))
    val fileSource = FileSource(name = "audio.wav", source = source)
  10. Initialize the OpenAI client

    main

    You can create an instance of the OpenAI client in two ways: by passing parameters directly to the constructor or by providing a pre-configured OpenAIConfig object. It is recommended to use environment variables for your API key for security.

    // Option 1: Direct initialization
    val openai = OpenAI(
        token = "your-api-key",
        timeout = Timeout(socket = 60.seconds),
        // additional configurations...
    )
    
    // Option 2: Using OpenAIConfig
    val config = OpenAIConfig(
        token = apiKey,
        timeout = Timeout(socket = 60.seconds),
        // additional configurations...
    )
    
    val openAI = OpenAI(config)
  11. Manage Fine-tunes

    main

    The library provides access to the legacy fine-tunes API. You can create fine-tunes using a training file and a model, list existing fine-tunes, retrieve details, cancel ongoing processes, and monitor progress via events (either as a list or a Flow).

    // Create fine-tune
    val fineTune = openAI.fineTune(
        request = FineTuneRequest(
            trainingFile = FileId("file-abc123"),
            model = ModelId("ada")
        )
    )
    
    // Stream fine-tune events
    val eventsFlow: Flow<FineTuneEvent> = openAI.fineTuneEventsFlow(FineTuneId("ft-abc123"))