Skip to content

Commit 90fe830

Browse files
Adding gemini to the team
1 parent ba197a6 commit 90fe830

File tree

10 files changed

+206
-29
lines changed

10 files changed

+206
-29
lines changed

Package.resolved

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let package = Package(
1919
// Dependencies declare other packages that this package depends on.
2020
.package(url: "https://github.com/jamesrochabrun/SwiftOpenAI", branch: "main"),
2121
.package(url: "https://github.com/jamesrochabrun/SwiftAnthropic", branch: "main"),
22+
.package(url: "https://github.com/google/generative-ai-swift", branch: "main"),
2223
],
2324
targets: [
2425
// Targets are the basic building blocks of a package, defining a module or a test suite.
@@ -27,7 +28,8 @@ let package = Package(
2728
name: "PolyAI",
2829
dependencies: [
2930
.product(name: "SwiftOpenAI", package: "SwiftOpenAI"),
30-
.product(name: "SwiftAnthropic", package: "SwiftAnthropic")
31+
.product(name: "SwiftAnthropic", package: "SwiftAnthropic"),
32+
.product(name: "GoogleGenerativeAI", package: "generative-ai-swift")
3133
]),
3234
.testTarget(
3335
name: "PolyAITests",

PolyAIExample/PolyAIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PolyAIExample/PolyAIExample/ApiKeysIntroView.swift

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ struct ApiKeyIntroView: View {
1212

1313
@State private var anthropicAPIKey = ""
1414
@State private var openAIAPIKey = ""
15+
@State private var geminiAPIKey = ""
1516
@State private var anthropicConfigAdded: Bool = false
1617
@State private var openAIConfigAdded: Bool = false
18+
@State private var geminiConfigAdded: Bool = false
1719

1820
@State private var configurations: [LLMConfiguration] = []
1921

@@ -26,34 +28,24 @@ struct ApiKeyIntroView: View {
2628
VStack {
2729
Spacer()
2830
VStack(spacing: 24) {
29-
VStack(alignment: .leading) {
30-
HStack {
31-
TextField("Enter Anthropic API Key", text: $anthropicAPIKey)
32-
Button {
33-
configurations.append(.anthropic(apiKey: anthropicAPIKey))
34-
anthropicConfigAdded = true
35-
} label: {
36-
Image(systemName: "plus")
37-
}
38-
.disabled(anthropicAPIKey.isEmpty)
31+
LLMConfigurationView(
32+
provider: "Anthropic",
33+
configurationAdded: $anthropicConfigAdded,
34+
apiKey: $anthropicAPIKey) {
35+
configurations.append(.anthropic(apiKey: anthropicAPIKey))
3936
}
40-
Text("Anthropic added to PolyAI 🚀")
41-
.opacity(anthropicConfigAdded ? 1 : 0)
42-
}
43-
VStack(alignment: .leading) {
44-
HStack {
45-
TextField("Enter OpenAI API Key", text: $openAIAPIKey)
46-
Button {
47-
configurations.append(.openAI(.api(key: openAIAPIKey)))
48-
openAIConfigAdded = true
49-
} label: {
50-
Image(systemName: "plus")
51-
}
52-
.disabled(openAIAPIKey.isEmpty)
37+
LLMConfigurationView(
38+
provider: "OpenAI",
39+
configurationAdded: $openAIConfigAdded,
40+
apiKey: $openAIAPIKey) {
41+
configurations.append(.openAI(.api(key: openAIAPIKey)))
42+
}
43+
LLMConfigurationView(
44+
provider: "Gemini",
45+
configurationAdded: $geminiConfigAdded,
46+
apiKey: $geminiAPIKey) {
47+
configurations.append(.gemini(apiKey: geminiAPIKey))
5348
}
54-
Text("OpenAI added to PolyAI 🚀")
55-
.opacity(openAIConfigAdded ? 1 : 0)
56-
}
5749
}
5850
.buttonStyle(.bordered)
5951
.padding()
@@ -76,6 +68,32 @@ struct ApiKeyIntroView: View {
7668
}
7769
}
7870

71+
struct LLMConfigurationView: View {
72+
73+
let provider: String
74+
@Binding var configurationAdded: Bool
75+
@Binding var apiKey: String
76+
let addConfig: () -> Void
77+
78+
79+
var body: some View {
80+
VStack(alignment: .leading) {
81+
HStack {
82+
TextField("Enter \(provider) API Key", text: $apiKey)
83+
Button {
84+
addConfig()
85+
configurationAdded = true
86+
} label: {
87+
Image(systemName: "plus")
88+
}
89+
.disabled(apiKey.isEmpty)
90+
}
91+
Text("\(provider) added to PolyAI 🚀")
92+
.opacity(configurationAdded ? 1 : 0)
93+
}
94+
}
95+
}
96+
7997
#Preview {
8098
ApiKeyIntroView()
8199
}

PolyAIExample/PolyAIExample/MessageDemoView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct MessageDemoView: View {
2323
enum LLM {
2424
case openAI
2525
case anthropic
26+
case gemini
2627
}
2728

2829
var body: some View {
@@ -74,6 +75,11 @@ struct MessageDemoView: View {
7475
.init(role: .user, content: prompt)
7576
],
7677
maxTokens: 1024)
78+
case .gemini:
79+
parameters = .gemini(
80+
model: "gemini-pro", messages: [
81+
.init(role: .user, content: prompt)
82+
], maxTokens: 2000)
7783
}
7884
try await observable.streamMessage(parameters: parameters)
7985
}
@@ -89,6 +95,7 @@ struct MessageDemoView: View {
8995
Picker("Options", selection: $selectedSegment) {
9096
Text("Anthropic").tag(LLM.anthropic)
9197
Text("OpenAI").tag(LLM.openAI)
98+
Text("Gemini").tag(LLM.gemini)
9299
}
93100
.pickerStyle(SegmentedPickerStyle())
94101
.padding()

Sources/PolyAI/Interfaces/Parameters/LLMParameter.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,19 @@ public enum LLMParameter {
2626
/// - maxTokens: The maximum number of tokens to generate.
2727
case anthropic(model: SwiftAnthropic.Model, messages: [LLMMessage], maxTokens: Int)
2828

29+
/// Represents a configuration for interacting with Gemini's models.
30+
/// - Parameters:
31+
/// - model: The specific model of Gemini to use.
32+
/// - messages: An array of messages to send to the model.
33+
/// - maxTokens: The maximum number of tokens to generate.
34+
case gemini(model: String, messages: [LLMMessage], maxTokens: Int)
35+
2936
/// A computed property that returns the name of the LLM service based on the case.
3037
var llmService: String {
3138
switch self {
3239
case .openAI: return "OpenAI"
3340
case .anthropic: return "Anthropic"
41+
case .gemini: return "Gemini"
3442
}
3543
}
3644
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// LLMMessageResponse+Gemini.swift
3+
//
4+
//
5+
// Created by James Rochabrun on 5/3/24.
6+
//
7+
8+
import Foundation
9+
import GoogleGenerativeAI
10+
11+
// MARK: Gemini
12+
13+
extension GenerateContentResponse: LLMMessageResponse {
14+
15+
public var id: String {
16+
UUID().uuidString
17+
}
18+
19+
public var model: String {
20+
""
21+
}
22+
23+
public var role: String {
24+
candidates.first?.content.role ?? "user"
25+
}
26+
27+
public var createdAt: Int? {
28+
nil
29+
}
30+
31+
public var contentDescription: String {
32+
text ?? ""
33+
}
34+
35+
public var usageMetrics: UsageMetrics {
36+
ChatUsageMetrics(
37+
inputTokens: usageMetadata?.promptTokenCount ?? 0,
38+
outputTokens: usageMetadata?.candidatesTokenCount ?? 0,
39+
totalTokens: usageMetadata?.totalTokenCount ?? 0)
40+
}
41+
42+
public var tools: [ToolUsage] {
43+
[]
44+
}
45+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// LLMMessageStreamResponse+Gemini.swift
3+
//
4+
//
5+
// Created by James Rochabrun on 5/4/24.
6+
//
7+
8+
import Foundation
9+
import GoogleGenerativeAI
10+
11+
// MARK: Gemini
12+
13+
extension GenerateContentResponse: LLMMessageStreamResponse {
14+
15+
public var content: String? {
16+
text
17+
}
18+
}

Sources/PolyAI/Service/DefaultPolyAIService.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,29 @@
66
//
77

88
import Foundation
9+
import GoogleGenerativeAI
910
import SwiftAnthropic
1011
import SwiftOpenAI
1112

13+
// MARK: Error
14+
1215
enum PolyAIError: Error {
1316
case missingLLMConfiguration(String)
1417
}
1518

19+
// MARK: Gemini specific
20+
21+
extension GenerativeModel {
22+
public struct Configuration {
23+
let apiKey: String
24+
}
25+
}
26+
1627
struct DefaultPolyAIService: PolyAIService {
1728

1829
private var openAIService: OpenAIService?
1930
private var anthropicService: AnthropicService?
31+
private var gemini: GenerativeModel.Configuration?
2032

2133
init(configurations: [LLMConfiguration])
2234
{
@@ -36,6 +48,9 @@ struct DefaultPolyAIService: PolyAIService {
3648

3749
case .anthropic(let apiKey, let configuration):
3850
anthropicService = AnthropicServiceFactory.service(apiKey: apiKey, configuration: configuration)
51+
52+
case .gemini(let apiKey):
53+
gemini = .init(apiKey: apiKey)
3954
}
4055
}
4156
}
@@ -54,6 +69,7 @@ struct DefaultPolyAIService: PolyAIService {
5469
let messageParams: [SwiftOpenAI.ChatCompletionParameters.Message] = messages.map { .init(role: .init(rawValue: $0.role) ?? .user, content: .text($0.content)) }
5570
let messageParameter = ChatCompletionParameters(messages: messageParams, model: model, maxTokens: maxTokens)
5671
return try await openAIService.startChat(parameters: messageParameter)
72+
5773
case .anthropic(let model, let messages, let maxTokens):
5874
guard let anthropicService else {
5975
throw PolyAIError.missingLLMConfiguration("You Must provide a valid configuration for the \(parameter.llmService) API")
@@ -71,6 +87,24 @@ struct DefaultPolyAIService: PolyAIService {
7187
let systemMessage = messages.first { $0.role == "system" }
7288
let messageParameter = MessageParameter(model: model, messages: messageParams, maxTokens: maxTokens, system: systemMessage?.content, stream: false)
7389
return try await anthropicService.createMessage(messageParameter)
90+
91+
case .gemini(let model, let messages, let maxTokens):
92+
guard let gemini else {
93+
throw PolyAIError.missingLLMConfiguration("You Must provide a valid configuration for the \(parameter.llmService) API")
94+
}
95+
let systemInstruction: ModelContent?
96+
if let systemMessage = messages.first(where: { message in
97+
message.role == "system"
98+
})?.content {
99+
systemInstruction = ModelContent(parts: [.text(systemMessage)])
100+
} else {
101+
systemInstruction = nil
102+
}
103+
let generativeModel = GenerativeModel(name: model, apiKey: gemini.apiKey, generationConfig: .init(GenerationConfig(maxOutputTokens: maxTokens)), systemInstruction: systemInstruction)
104+
let userMessage = messages.first { message in
105+
message.role == "user"
106+
}?.content ?? ""
107+
return try await generativeModel.generateContent(userMessage)
74108
}
75109
}
76110

@@ -85,8 +119,11 @@ struct DefaultPolyAIService: PolyAIService {
85119
}
86120
let messageParams: [SwiftOpenAI.ChatCompletionParameters.Message] = messages.map { .init(role: .init(rawValue: $0.role) ?? .user, content: .text($0.content)) }
87121
let messageParameter = ChatCompletionParameters(messages: messageParams, model: model, maxTokens: maxTokens)
122+
123+
88124
let stream = try await openAIService.startStreamedChat(parameters: messageParameter)
89125
return try mapToLLMMessageStreamResponse(stream: stream)
126+
90127
case .anthropic(let model, let messages, let maxTokens):
91128
guard let anthropicService else {
92129
throw PolyAIError.missingLLMConfiguration("You Must provide a valid configuration for the \(parameter.llmService) API")
@@ -105,6 +142,25 @@ struct DefaultPolyAIService: PolyAIService {
105142
let messageParameter = MessageParameter(model: model, messages: messageParams, maxTokens: maxTokens, system: systemMessage?.content)
106143
let stream = try await anthropicService.streamMessage(messageParameter)
107144
return try mapToLLMMessageStreamResponse(stream: stream)
145+
146+
case .gemini(let model, let messages, let maxTokens):
147+
guard let gemini else {
148+
throw PolyAIError.missingLLMConfiguration("You Must provide a valid configuration for the \(parameter.llmService) API")
149+
}
150+
let systemInstruction: ModelContent?
151+
if let systemMessage = messages.first(where: { message in
152+
message.role == "system"
153+
})?.content {
154+
systemInstruction = ModelContent(parts: [.text(systemMessage)])
155+
} else {
156+
systemInstruction = nil
157+
}
158+
let generativeModel = GenerativeModel(name: model, apiKey: gemini.apiKey, generationConfig: .init(GenerationConfig(maxOutputTokens: maxTokens)), systemInstruction: systemInstruction)
159+
let userMessage = messages.first { message in
160+
message.role == "user"
161+
}?.content ?? ""
162+
let stream = generativeModel.generateContentStream(userMessage)
163+
return try mapToLLMMessageStreamResponse(stream: stream)
108164
}
109165
}
110166

Sources/PolyAI/Service/PolyAIService.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public enum LLMConfiguration {
4141
/// - apiKey: The API key for authenticating requests to Anthropic.
4242
/// - configuration: The URLSession configuration to use for network requests. Defaults to `.default`.
4343
case anthropic(apiKey: String, configuration: URLSessionConfiguration = .default)
44+
45+
/// Configuration for accessing Gemini's API.
46+
/// - Parameters:
47+
/// - apiKey: The API key for authenticating requests to Gemini.
48+
case gemini(apiKey: String)
4449
}
4550

4651
/// Defines the interface for a service that interacts with Large Language Models (LLMs).

0 commit comments

Comments
 (0)