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

feat: Assistants API #1

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions Demo/App/APIProvidedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ struct APIProvidedView: View {
@Binding var apiKey: String
@StateObject var chatStore: ChatStore
@StateObject var imageStore: ImageStore
@StateObject var assistantStore: AssistantStore
@StateObject var miscStore: MiscStore

@State var isShowingAPIConfigModal: Bool = true

@Environment(\.idProviderValue) var idProvider
Expand All @@ -35,6 +37,12 @@ struct APIProvidedView: View {
openAIClient: OpenAI(apiToken: apiKey.wrappedValue)
)
)
self._assistantStore = StateObject(
wrappedValue: AssistantStore(
openAIClient: OpenAI(apiToken: apiKey.wrappedValue),
idProvider: idProvider
)
)
self._miscStore = StateObject(
wrappedValue: MiscStore(
openAIClient: OpenAI(apiToken: apiKey.wrappedValue)
Expand All @@ -46,12 +54,14 @@ struct APIProvidedView: View {
ContentView(
chatStore: chatStore,
imageStore: imageStore,
assistantStore: assistantStore,
miscStore: miscStore
)
.onChange(of: apiKey) { newApiKey in
let client = OpenAI(apiToken: newApiKey)
chatStore.openAIClient = client
imageStore.openAIClient = client
assistantStore.openAIClient = client
miscStore.openAIClient = client
}
}
Expand Down
29 changes: 17 additions & 12 deletions Demo/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,58 @@ import SwiftUI
struct ContentView: View {
@ObservedObject var chatStore: ChatStore
@ObservedObject var imageStore: ImageStore
@ObservedObject var assistantStore: AssistantStore
@ObservedObject var miscStore: MiscStore

@State private var selectedTab = 0
@Environment(\.idProviderValue) var idProvider

var body: some View {
TabView(selection: $selectedTab) {
ChatView(
store: chatStore
store: chatStore,
assistantStore: assistantStore
)
.tabItem {
Label("Chats", systemImage: "message")
}
.tag(0)

AssistantsView(
store: chatStore,
assistantStore: assistantStore
)
.tabItem {
Label("Assistants", systemImage: "eyeglasses")
}
.tag(1)

TranscribeView(
)
.tabItem {
Label("Transcribe", systemImage: "mic")
}
.tag(1)
.tag(2)

ImageView(
store: imageStore
)
.tabItem {
Label("Image", systemImage: "photo")
}
.tag(2)
.tag(3)

MiscView(
store: miscStore
)
.tabItem {
Label("Misc", systemImage: "ellipsis")
}
.tag(3)
.tag(4)
}
}
}

struct ChatsView: View {
var body: some View {
Text("Chats")
.font(.largeTitle)
}
}

struct TranscribeView: View {
var body: some View {
Text("Transcribe: TBD")
Expand Down
6 changes: 4 additions & 2 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -286,6 +287,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;
Expand Down Expand Up @@ -315,7 +317,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
Expand Down Expand Up @@ -354,7 +356,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
Expand Down
125 changes: 125 additions & 0 deletions Demo/DemoChat/Sources/AssistantStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// ChatStore.swift
// DemoChat
//
// Created by Sihao Lu on 3/25/23.
//

import Foundation
import Combine
import OpenAI

public final class AssistantStore: ObservableObject {
public var openAIClient: OpenAIProtocol
let idProvider: () -> String
@Published var selectedAssistantId: String?

@Published var availableAssistants: [Assistant] = []

public init(
openAIClient: OpenAIProtocol,
idProvider: @escaping () -> String
) {
self.openAIClient = openAIClient
self.idProvider = idProvider
}

// MARK: Models

@MainActor
func createAssistant(name: String, description: String, instructions: String, codeInterpreter: Bool, retrievel: Bool, fileIds: [String]? = nil) async -> String? {
do {
let tools = createToolsArray(codeInterpreter: codeInterpreter, retrieval: retrievel)
let query = AssistantsQuery(model: Model.gpt4_1106_preview, name: name, description: description, instructions: instructions, tools:tools, fileIds: fileIds)
let response = try await openAIClient.assistants(query: query, method: "POST", after: nil)

// Refresh assistants with one just created (or modified)
let _ = await getAssistants()

// Returns assistantId
return response.id

} catch {
// TODO: Better error handling
print(error.localizedDescription)
}
return nil
}

@MainActor
func modifyAssistant(asstId: String, name: String, description: String, instructions: String, codeInterpreter: Bool, retrievel: Bool, fileIds: [String]? = nil) async -> String? {
do {
let tools = createToolsArray(codeInterpreter: codeInterpreter, retrieval: retrievel)
let query = AssistantsQuery(model: Model.gpt4_1106_preview, name: name, description: description, instructions: instructions, tools:tools, fileIds: fileIds)
let response = try await openAIClient.assistantModify(query: query, asstId: asstId)

// Returns assistantId
return response.id

} catch {
// TODO: Better error handling
print(error.localizedDescription)
}
return nil
}

@MainActor
func getAssistants(limit: Int = 20, after: String? = nil) async -> [Assistant] {
do {
let response = try await openAIClient.assistants(query: nil, method: "GET", after: after)

var assistants = [Assistant]()
for result in response.data ?? [] {
let codeInterpreter = result.tools?.filter { $0.toolType == "code_interpreter" }.first != nil
let retrieval = result.tools?.filter { $0.toolType == "retrieval" }.first != nil
let fileIds = result.fileIds ?? []

assistants.append(Assistant(id: result.id, name: result.name, description: result.description, instructions: result.instructions, codeInterpreter: codeInterpreter, retrieval: retrieval, fileIds: fileIds))
}
if after == nil {
availableAssistants = assistants
}
else {
availableAssistants = availableAssistants + assistants
}
return assistants

} catch {
// TODO: Better error handling
print(error.localizedDescription)
}
return []
}

func selectAssistant(_ assistantId: String?) {
selectedAssistantId = assistantId
}

@MainActor
func uploadFile(url: URL) async -> FilesResult? {
do {

let mimeType = url.mimeType()

let fileData = try Data(contentsOf: url)

let result = try await openAIClient.files(query: FilesQuery(purpose: "assistants", file: fileData, fileName: url.lastPathComponent, contentType: mimeType))
return result
}
catch {
print("error = \(error)")
return nil
}
}

func createToolsArray(codeInterpreter: Bool, retrieval: Bool) -> [Tool] {
var tools = [Tool]()
if codeInterpreter {
tools.append(Tool(toolType: "code_interpreter"))
}
if retrieval {
tools.append(Tool(toolType: "retrieval"))
}
return tools
}
}
Loading