Skip to content
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
6 changes: 6 additions & 0 deletions Playground/Playground/Views/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ struct AppView: View {
ResponseFormatView(provider: provider)
}
}

if provider == .openRouter {
NavigationLink("Fallback Model") {
FallbackModelView(provider: provider)
}
}
}
.disabled(provider == .openai && viewModel.openaiAPIKey.isEmpty)
.disabled(provider == .openRouter && viewModel.openRouterAPIKey.isEmpty)
Expand Down
150 changes: 150 additions & 0 deletions Playground/Playground/Views/FallbackModelView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// FallbackModelView.swift
// Playground
//
// Created by Kevin Hermawan on 10/19/24.
//

import SwiftUI
import LLMChatOpenAI

struct FallbackModelView: View {
let provider: ServiceProvider

@Environment(AppViewModel.self) private var viewModel
@State private var isPreferencesPresented: Bool = false

@State private var fallbackModel: String = ""

@State private var prompt: String = "Hi!"
@State private var response: String = ""
@State private var inputTokens: Int = 0
@State private var outputTokens: Int = 0
@State private var totalTokens: Int = 0

var body: some View {
@Bindable var viewModelBindable = viewModel

VStack {
Form {
Section("Models") {
Picker("Primary Model", selection: $viewModelBindable.selectedModel) {
ForEach(viewModelBindable.models, id: \.self) { model in
Text(model).tag(model)
}
}

Picker("Fallback Model", selection: $fallbackModel) {
ForEach(viewModelBindable.models, id: \.self) { model in
Text(model).tag(model)
}
}
}
.disabled(viewModel.models.isEmpty)

Section("Prompt") {
TextField("Prompt", text: $prompt)
}

Section("Response") {
Text(response)
}

UsageSection(inputTokens: inputTokens, outputTokens: outputTokens, totalTokens: totalTokens)
}

VStack {
SendButton(stream: viewModel.stream, onSend: onSend, onStream: onStream)
.disabled(viewModel.models.isEmpty)
}
}
.toolbar {
ToolbarItem(placement: .principal) {
NavigationTitle("Fallback Model")
}

ToolbarItem(placement: .primaryAction) {
Button("Preferences", systemImage: "gearshape", action: { isPreferencesPresented.toggle() })
}
}
.sheet(isPresented: $isPreferencesPresented) {
PreferencesView()
}
.onAppear {
viewModel.setup(for: provider)
}
.onDisappear {
viewModel.selectedModel = ""
}
.onChange(of: viewModel.models) { _, models in
if let firstModel = models.first {
fallbackModel = firstModel
}
}
}

private func onSend() {
clear()

let messages = [
ChatMessage(role: .system, content: viewModel.systemPrompt),
ChatMessage(role: .user, content: prompt)
]

let options = ChatOptions(temperature: viewModel.temperature)

Task {
do {
let completion = try await viewModel.chat.send(models: [viewModel.selectedModel, fallbackModel], messages: messages, options: options)

if let content = completion.choices.first?.message.content {
self.response = content
}

if let usage = completion.usage {
self.inputTokens = usage.promptTokens
self.outputTokens = usage.completionTokens
self.totalTokens = usage.totalTokens
}
} catch {
print(String(describing: error))
}
}
}

private func onStream() {
clear()

let messages = [
ChatMessage(role: .system, content: viewModel.systemPrompt),
ChatMessage(role: .user, content: prompt)
]

let options = ChatOptions(temperature: viewModel.temperature)

Task {
do {
for try await chunk in viewModel.chat.stream(models: [viewModel.selectedModel, fallbackModel], messages: messages, options: options) {
if let content = chunk.choices.first?.delta.content {
self.response += content
}

if let usage = chunk.usage {
self.inputTokens = usage.promptTokens ?? 0
self.outputTokens = usage.completionTokens ?? 0
self.totalTokens = usage.totalTokens ?? 0
}
}
} catch {
print(String(describing: error))
}
}
}

private func clear() {
response = ""
inputTokens = 0
outputTokens = 0
totalTokens = 0
}
}
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,34 @@ let task = Task {
task.cancel()
```

#### Using Fallback Models (OpenRouter only)

```swift
Task {
do {
let completion = try await chat.send(models: ["openai/gpt-4o", "mistralai/mixtral-8x7b-instruct"], messages: messages)

print(completion.choices.first?.message.content ?? "No response")
} catch {
print(String(describing: error))
}
}

Task {
do {
for try await chunk in chat.stream(models: ["openai/gpt-4o", "mistralai/mixtral-8x7b-instruct"], messages: messages) {
if let content = chunk.choices.first?.delta.content {
print(content, terminator: "")
}
}
} catch {
print(String(describing: error))
}
}
```

> **Note**: Fallback model functionality is only supported when using OpenRouter. If you use the fallback models method (`send(models:)` or `stream(models:)`) with other providers, only the first model in the array will be used, and the rest will be ignored. To learn more about fallback models, check out the [OpenRouter documentation](https://openrouter.ai/docs/model-routing).

### Advanced Usage

#### Vision
Expand Down
28 changes: 28 additions & 0 deletions Sources/LLMChatOpenAI/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ let task = Task {
task.cancel()
```

#### Using Fallback Models (OpenRouter only)

```swift
Task {
do {
let completion = try await chat.send(models: ["openai/gpt-4o", "mistralai/mixtral-8x7b-instruct"], messages: messages)

print(completion.choices.first?.message.content ?? "No response")
} catch {
print(String(describing: error))
}
}

Task {
do {
for try await chunk in chat.stream(models: ["openai/gpt-4o", "mistralai/mixtral-8x7b-instruct"], messages: messages) {
if let content = chunk.choices.first?.delta.content {
print(content, terminator: "")
}
}
} catch {
print(String(describing: error))
}
}
```

> **Note**: Fallback model functionality is only supported when using OpenRouter. If you use the fallback models method (`send(models:)` or `stream(models:)`) with other providers, only the first model in the array will be used, and the rest will be ignored. To learn more about fallback models, check out the [OpenRouter documentation](https://openrouter.ai/docs/model-routing).

### Advanced Usage

#### Vision
Expand Down
Loading