dylanshine / openai-kit

A community Swift package used to interact with the OpenAI API
https://platform.openai.com/docs/api-reference
MIT License
692 stars 107 forks source link

Add basic support for functions #60

Open carlo- opened 1 year ago

carlo- commented 1 year ago

Hi all! I've started working on adding support for functions. I have a rather rough implementation ready, so I wanted to check in here. Function calls from the models are inherently "unsafe", so this clashes a bit with the strict type safety of Swift; I think making this work in a neat way is an interesting challenge. What are your thoughts?

Here's how you'd define a function with my implementation (this example function can be used to search a database of recipes):

struct FindRecipeFunction: Chat.Function {

    struct Call: Chat.FunctionCallWithArguments {
        var name: String
        var arguments: Arguments

        struct Arguments: Codable {
            let query: String
        }
    }

    struct Parameters: Encodable {
        let type = "object"
        let properties = Props()
        let required = ["query"]

        struct Props: Encodable {
            let query = Query()

            struct Query: Encodable {
                let type = "string"
                let description = "A human readable query to use when searching for a recipe."
            }
        }
    }

    let name: String = "findRecipe"

    let description: String? = "The `findRecipe` API can be used to search through a database of recipes."

    let parameters: Parameters? = Parameters()
}

You would then send a chat request as usual, passing in the functions and the function "mode" (called "function_call" in the docs):

let chat = try await client.chats.create(
    model: Model.GPT4.gpt40613,
    messages: ...,
    functions: [FindRecipeFunction()],
    functionMode: .auto
)
let message = chat.choices[0].message

And then try to extract any function calls with type safety:

if case let .assistantWithCall(_, call) = message,
    let call = try? call.structured(as: FindRecipeFunction.Call.self) {

    let recipeQuery = call.arguments.query
    // ...
}
ronaldmannak commented 1 year ago

Could the type property in Parameters be an enum instead of string? I'm also wondering if you could make it less verbose by using generics for arguments and parameters, though I'm not sure how you would add the description in that case.

Also, what's the difference between assistantWithCall(content: String?, call: UnstructuredFunctionCall) and function I see in a switch statement?

carlo- commented 1 year ago

Unfortunately I haven't had any time to come back to this, and looks like I won't be able to in the near future either, but feel free to edit any part of this PR 😊