Skip to content

Commit

Permalink
Implement Chat Completion Function Call options
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoDotIO committed Oct 10, 2023
1 parent ede4fc3 commit 8553a29
Show file tree
Hide file tree
Showing 22 changed files with 535 additions and 23 deletions.
4 changes: 4 additions & 0 deletions Examples/iOS_Example/iOS_Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
7D3E2ED92A7F262A0014174B /* ChatMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3E2ED82A7F262A0014174B /* ChatMainView.swift */; };
7D4A91082A87DE7E000A138A /* AudioPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4A91072A87DE7E000A138A /* AudioPlayerViewModel.swift */; };
7D4A910A2A87E110000A138A /* audio_translation.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7D4A91092A87E110000A138A /* audio_translation.mp3 */; };
7D76E4F82AD46B7500E2F4C3 /* CreateChatFunctionCallExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D76E4F72AD46B7500E2F4C3 /* CreateChatFunctionCallExample.swift */; };
7DC0E9082A87CF97005C8763 /* CreateTranscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC0E9072A87CF97005C8763 /* CreateTranscription.swift */; };
7DC0E90A2A87D046005C8763 /* audio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7DC0E9092A87D046005C8763 /* audio.mp3 */; };
7DDE422E29AFE567000BBB01 /* CreateChatCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDE422D29AFE567000BBB01 /* CreateChatCompletion.swift */; };
Expand Down Expand Up @@ -92,6 +93,7 @@
7D3E2ED82A7F262A0014174B /* ChatMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMainView.swift; sourceTree = "<group>"; };
7D4A91072A87DE7E000A138A /* AudioPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerViewModel.swift; sourceTree = "<group>"; };
7D4A91092A87E110000A138A /* audio_translation.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = audio_translation.mp3; sourceTree = "<group>"; };
7D76E4F72AD46B7500E2F4C3 /* CreateChatFunctionCallExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateChatFunctionCallExample.swift; sourceTree = "<group>"; };
7DC0E9072A87CF97005C8763 /* CreateTranscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateTranscription.swift; sourceTree = "<group>"; };
7DC0E9092A87D046005C8763 /* audio.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = audio.mp3; path = ../audio.mp3; sourceTree = "<group>"; };
7DDE422D29AFE567000BBB01 /* CreateChatCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateChatCompletion.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -282,6 +284,7 @@
7DDE422D29AFE567000BBB01 /* CreateChatCompletion.swift */,
7D3E2ED62A7F228E0014174B /* CreateChatCompletionStreaming.swift */,
7D3E2ED82A7F262A0014174B /* ChatMainView.swift */,
7D76E4F72AD46B7500E2F4C3 /* CreateChatFunctionCallExample.swift */,
);
path = Chat;
sourceTree = "<group>";
Expand Down Expand Up @@ -373,6 +376,7 @@
761C251529403BE4005066C2 /* FinetuneMainView.swift in Sources */,
761C252A29403FB7005066C2 /* RetrieveFinetuneExample.swift in Sources */,
761C252629403F3F005066C2 /* RetrieveFileContentsExample.swift in Sources */,
7D76E4F82AD46B7500E2F4C3 /* CreateChatFunctionCallExample.swift in Sources */,
765BB1DD29405126000BF124 /* ModelMainView.swift in Sources */,
76C0C26A293FC8FC003075D2 /* ContentPolicyExample.swift in Sources */,
765BB1D929404D52000BF124 /* CompletionMainView.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Examples/iOS_Example/iOS_Example/Chat/ChatMainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ struct ChatMainView: View {
NavigationLink(destination: CreateChatCompletionStreamingExample()) {
Text("Generate Chat Completion Streaming Example")
}

NavigationLink(destination: CreateChatFunctionCallExample()) {
Text("Generate Chat Completion Function Call Example")
}
}
.listStyle(.plain)
.navigationTitle("Chat")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct CreateChatCompletionExample: View {
VStack {
VStack(alignment: .leading) {
ForEach(chat) { message in
Text("\(message.role.rawValue.capitalized): \(message.content)")
Text("\(message.role.rawValue.capitalized): \(message.content ?? "NO CONTENT")")
.padding(.vertical, 10)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct CreateChatCompletionStreamingExample: View {
VStack {
VStack(alignment: .leading) {
ForEach(chat) { message in
Text("\(message.role.rawValue.capitalized): \(message.content)")
Text("\(message.role.rawValue.capitalized): \(message.content ?? "NO CONTENT")")
.padding(.vertical, 10)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// CreateChatFunctionCallExample.swift
// iOS_Example
//
// Created by Marcus Arnett on 10/9/23.
//

import SwiftUI
import OpenAIKit

struct CreateChatFunctionCallExample: View {
@State private var isCompleting: Bool = false
@State private var weatherInfo: WeatherInfo? = nil

enum TemperatureUnit: String, Codable {
case fahrenheit
case celsius
}

struct WeatherInfo: Codable {
let location: String
let temperature: String
let unit: TemperatureUnit
let forecast: [String]
}

func getCurrentWeather(location: String, unit: TemperatureUnit = .fahrenheit) -> WeatherInfo {
return WeatherInfo(location: location, temperature: "72", unit: unit, forecast: ["sunny", "windy"])
}

let messages: [ChatMessage] = [ChatMessage(role: .user, content: "What's the weather like in Boston?")]

let functions: [Function] = [
Function(
name: "getCurrentWeather",
description: "Get the current weather in a given location",
parameters: Parameters(
type: "object",
properties: [
"location": ParameterDetail(type: "string", description: "The city and state, e.g. San Francisco, CA"),
"unit": ParameterDetail(type: "string", enumValues: ["fahrenheit", "celsius"])
],
required: ["location"]
)
)
]

var body: some View {
VStack {
VStack(alignment: .leading) {
ForEach(messages) { message in
Text("\(message.role.rawValue.capitalized): \(message.content ?? "NO CONTENT")")
.padding(.vertical, 10)
}
}
.padding(20)

if isCompleting {
VStack {
if let weatherInfo {
Text("Assistant: The current weather in \(weatherInfo.location) is \(weatherInfo.temperature) degrees \(weatherInfo.unit.rawValue).")
} else {
Text("Assistant: ...")
}
}
.padding()
} else {
VStack {
Button {
isCompleting = true

Task {
do {
let config = Configuration(
organizationId: "INSERT-ORGANIZATION-ID",
apiKey: "INSERT-API-KEY"
)
let openAI = OpenAI(config)
let chatParameters = ChatParameters(
model: .chatGPTTurbo,
messages: messages,
functionCall: "auto",
functions: functions
)
let chatCompletion = try await openAI.generateChatCompletion(
parameters: chatParameters
)

if let message = chatCompletion.choices[0].message, let functionCall = message.functionCall {
let jsonString = functionCall.arguments
if let data = jsonString.data(using: .utf8) {
do {
if
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let location = json["location"] as? String
{
self.weatherInfo = self.getCurrentWeather(location: location)
}
} catch {
print("Error parsing JSON: \(error)")
}
}
}
} catch {
print("ERROR DETAILS - \(error)")
}
}
} label: {
Text("Generate Completion")
.font(.headline)
.foregroundColor(.white)
.frame(width: 270, height: 50)
.background(.blue)
.clipShape(Capsule())
.padding(.top, 8)
}
}
}
}

Spacer()
}
}

struct CreateChatFunctionCallExample_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
CreateChatFunctionCallExample()
}
}
}
2 changes: 1 addition & 1 deletion [email protected]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// swift-tools-version: 5.7
//
// Package.swift
// Package@swift-5.7.swift
// OpenAIKit
//
// Copyright (c) 2023 MarcoDotIO
Expand Down
2 changes: 1 addition & 1 deletion [email protected]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// swift-tools-version: 5.8
//
// Package.swift
// Package@swift-5.8.swift
// OpenAIKit
//
// Copyright (c) 2023 MarcoDotIO
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ OpenAIKit is a community-maintained API for the OpenAI REST endpoint used to get

| Platform | Minimum Swift Version | Installation | Status |
| --- | --- | --- | --- |
| iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ | 5.5 | [Swift Package Manager](#swift-package-manager) | Fully Tested |
| iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ / visionOS 1.0+ | 5.5 | [Swift Package Manager](#swift-package-manager) | Fully Tested |

## Installation

Expand Down
21 changes: 21 additions & 0 deletions Sources/OpenAIKit/Extensions/StringPropertyMap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// SwiftUIView.swift
//
//
// Created by Marcus Arnett on 10/10/23.
//

import SwiftUI

internal extension [String: Property] {
var body: [String: Any] {
var result: [String: Any] = [:]
for key in self.keys {
let value = self[key]
if let value {
result[key] = value.body
}
}
return result
}
}
33 changes: 33 additions & 0 deletions Sources/OpenAIKit/Protocols/Chat/Property.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Property.swift
// OpenAIKit
//
// Copyright (c) 2023 MarcoDotIO
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation

/// The properties used for a function call
public protocol Property: Codable {
/// The type used for the property
var type: String { get }
var body: [String: Any] { get }
}
1 change: 0 additions & 1 deletion Sources/OpenAIKit/Protocols/OpenAIProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public protocol OpenAIProtocol {
/// Creates a completion for the provided prompt and parameters.
/// - Parameter param: A `EditParameters` object containing the parameters for the call.
/// - Returns: An `EditResponse` object.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
func generateEdit(parameters param: EditParameters) async throws -> EditResponse


Expand Down
2 changes: 0 additions & 2 deletions Sources/OpenAIKit/Types/Enums/Chat/ChatModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ public enum ChatModels: String, CustomStringConvertible {
case chatGPTTurbo16k0613 = "gpt-3.5-turbo-16k-0613"

/// Can do any language task with better quality, longer output, and consistent instruction-following than the curie, babbage, or ada models. Also supports some additional features such as [inserting text](https://platform.openai.com/docs/guides/gpt/inserting-text).
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case textDavinci003 = "text-davinci-003"

/// Similar capabilities to text-davinci-003 but trained with supervised fine-tuning instead of reinforcement learning
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case textDavinci002 = "text-davinci-002"

// MARK: GPT-4 Models
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,24 @@ import Foundation
/// Models used for the Completion endpoint
public enum CompletionModels: String, CustomStringConvertible {
/// Very capable, faster and lower cost than Davinci.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case textCurie001 = "text-curie-001"

/// Capable of straightforward tasks, very fast, and lower cost.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case textBabbage001 = "text-babbage-001"

/// Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case textAda001 = "text-ada-001"

/// Most capable GPT-3 model. Can do any task the other models can do, often with higher quality.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case davinci

/// Very capable, but faster and lower cost than Davinci.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case curie

/// Capable of straightforward tasks, very fast, and lower cost.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case babbage

/// Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.
@available(*, deprecated, message: "On July 06, 2023, OpenAI announced the upcoming retirements of older GPT-3 and GPT-3.5 models served via the completions endpoint. OpenAI also announced the upcoming retirement of their first-generation text embedding models. They will be shut down on January 04, 2024.")
case ada

/// The maximum tokens the models can read
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ public struct ChatParameters {
/// [Learn more.](https://beta.openai.com/docs/guides/safety-best-practices/end-user-ids)
public var user: String?

/// Controls how the model calls functions. "none" means the model will not call a function and instead generates a message.
/// "auto" means the model can pick between generating a message or calling a function. Specifying a particular function via
/// {"name": "my_function"} forces the model to call that function. "none" is the default when no functions are present. "auto" is
/// the default if functions are present.
public var functionCall: String?

/// A list of functions the model may generate JSON inputs for.
public var functions: [Function]?

public init(
model: ChatModels,
messages: [ChatMessage],
Expand All @@ -101,7 +110,9 @@ public struct ChatParameters {
presencePenalty: Double = 0.0,
frequencyPenalty: Double = 0.0,
logitBias: [String : Int]? = nil,
user: String? = nil
user: String? = nil,
functionCall: String? = nil,
functions: [Function]? = nil
) {
self.model = model
self.messages = messages
Expand All @@ -114,10 +125,12 @@ public struct ChatParameters {
self.frequencyPenalty = frequencyPenalty
self.logitBias = logitBias
self.user = user
self.functionCall = functionCall
self.functions = functions
}

/// The body of the URL used for OpenAI API requests.
internal var body: [String: Any] {
public var body: [String: Any] {
var result: [String: Any] = [
"model": self.model.description,
"temperature": self.temperature,
Expand All @@ -141,6 +154,14 @@ public struct ChatParameters {
result["user"] = user
}

if let functionCall = self.functionCall {
result["function_call"] = functionCall
}

if let functions = self.functions {
result["functions"] = functions.map { $0.body }
}

return result
}
}
Loading

0 comments on commit 8553a29

Please sign in to comment.