When starting a chat (model.startChat) you can include prior history. When there is no history, it would be nice to be able to add some history, for example a greeting from the chatbot. This is easily done by creating a ModelContent and assigning that to the chat's history. However, when calling a function, the process will fail after the function response step with an error if there are an odd number of items in history.
I've included a TestAgent class below. Simply create the class and watch the output. The only time it'll fail is when history contains an odd number of entries. I can include 10 model entries, or 10 user entries or a combination of each, as long as it's an even number. However, once I create history with an odd number, it fails.
Reproducing the issue
//
// TestAgent.swift
// Expense Tracker LLM
//
// Created by Shane Miller on 9/20/24.
//
import Foundation
import FirebaseVertexAI
class TestAgent {
var systemInstructions = """
Users will ask you information about exchange rates. Use your tools to answer them."
"""
private var modelName = "gemini-1.5-flash" // gemini-1.5-pro-exp-0801", //gemini-1.5-pro", //gemini-1.5-flash",
// Initialize the Vertex AI service
let vertex = VertexAI.vertexAI()
let config = GenerationConfig(
temperature: 0,
topP: 0.95,
topK: 40,
maxOutputTokens: 8192,
responseMIMEType: "text/plain"
)
init() {
Task {
await success1()
await success2()
await success3()
await failure1()
}
}
let getExchangeRate = FunctionDeclaration(
name: "getExchangeRate",
description: "Get the exchange rate for currencies between countries",
parameters: [
"currencyFrom": Schema(
type: .string,
description: "The currency to convert from."
),
"currencyTo": Schema(
type: .string,
description: "The currency to convert to."
),
],
requiredParameters: ["currencyFrom", "currencyTo"]
)
func makeAPIRequest(currencyFrom: String,
currencyTo: String) -> JSONObject {
// This hypothetical API returns a JSON such as:
// {"base":"USD","rates":{"SEK": 10.99}}
return [
"base": .string(currencyFrom),
"rates": .object([currencyTo: .number(10.99)]),
]
}
// succeeds because there are an even number of ModelContents user, model, user, model
func success1() async {
print("Success 1")
// create some fake history.
let mc1 = try! ModelContent(role: "user", "Hello, how are you?")
let mc2 = try! ModelContent(role: "model", "I'm great! How are you?")
let mc3 = try! ModelContent(role: "user", "I'm good. Thanks for asking.")
let mc4 = try! ModelContent(role: "model", "My pleasure. What can I do for you today?")
let history = [mc1, mc2, mc3, mc4]
let response = await test(history: history)
print("Success 1 \(response)")
}
// succeeds because there are an even number of ModelContents, even though they are both model
func success2() async {
print("Success 2")
// create some fake history.
let mc1 = try! ModelContent(role: "model", "Hello, how are you?")
let mc2 = try! ModelContent(role: "model", "I'm great! How are you?")
let history = [mc1, mc2]
let response = await test(history: history)
print("Success 2 \(response)")
}
// succeeds because there are an even number of ModelContents, even though they are both user
func success3() async {
print("Success 3")
// create some fake history.
let mc1 = try! ModelContent(role: "user", "What can I do for you today?")
let mc2 = try! ModelContent(role: "user", "What can I do for you today?")
let history = [mc1, mc2]
let response = await test(history: history)
print("Success 3 \(response)")
}
// fails because there are is an ODD number of ModelContents
func failure1() async {
print("failure1")
// create some fake history.
let mc1 = try! ModelContent(role: "model", "What can I do for you today?")
let history = [mc1]
let response = await test(history: history)
print("failure1 \(response)")
}
func test(history : [ModelContent]) async -> String {
let model = vertex.generativeModel(
modelName: modelName,
generationConfig: config,
tools: [Tool(functionDeclarations: [getExchangeRate])],
systemInstruction: ModelContent(role: "system", parts: self.systemInstructions)
)
let prompt = "How much is 50 US dollars worth in Swedish krona?"
let chat = await model.startChat(history: history)
do {
let response1 = try await chat.sendMessage(prompt)
// Check if the model responded with a function call
guard let functionCall = response1.functionCalls.first else {
fatalError("Model did not respond with a function call.")
}
// Print an error if the returned function was not declared
guard functionCall.name == "getExchangeRate" else {
fatalError("Unexpected function called: \(functionCall.name)")
}
// Verify that the names and types of the parameters match the declaration
guard case let .string(currencyFrom) = functionCall.args["currencyFrom"] else {
fatalError("Missing argument: currencyFrom")
}
guard case let .string(currencyTo) = functionCall.args["currencyTo"] else {
fatalError("Missing argument: currencyTo")
}
// Call the hypothetical API
let apiResponse = makeAPIRequest(currencyFrom: currencyFrom, currencyTo: currencyTo)
// Send the API response back to the model so it can generate a text response that can be
// displayed to the user.
let response = try await chat.sendMessage([ModelContent(
role: "function",
parts: [.functionResponse(FunctionResponse(
name: functionCall.name,
response: apiResponse
))]
)])
// Log the text response.
guard let modelResponse = response.text else {
fatalError("Model did not respond with text.")
}
return modelResponse
} catch {
print("Error sending message: \(error.localizedDescription)")
}
return ""
}
}
// let testAgent = TestAgent()
Firebase SDK Version
11.3
Xcode Version
16
Installation Method
Swift Package Manager
Firebase Product(s)
App Check, Authentication, Firestore, VertexAI
Targeted Platforms
iOS
Relevant Log Output
Success 1
Success 1 50 US dollars is worth 549.5 Swedish krona.
Success 2
Success 2 50 US dollars is worth 549.5 Swedish krona.
Success 3
Success 3 50 US dollars is worth 549.5 Swedish krona.
failure1
[FirebaseVertexAI] Response payload: {
"error": {
"code": 400,
"message": "Please ensure that function call turn comes immediately after a user turn or after a function response turn.",
"status": "INVALID_ARGUMENT"
}
}
Error sending message: The operation couldn’t be completed. (FirebaseVertexAI.GenerateContentError error 1.)
failure1
If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved snippet
```json
Replace this line with the contents of your Package.resolved.
```
If using CocoaPods, the project's Podfile.lock
Expand Podfile.lock snippet
```yml
Replace this line with the contents of your Podfile.lock!
```
Description
When starting a chat (model.startChat) you can include prior history. When there is no history, it would be nice to be able to add some history, for example a greeting from the chatbot. This is easily done by creating a ModelContent and assigning that to the chat's history. However, when calling a function, the process will fail after the function response step with an error if there are an odd number of items in history. I've included a TestAgent class below. Simply create the class and watch the output. The only time it'll fail is when history contains an odd number of entries. I can include 10 model entries, or 10 user entries or a combination of each, as long as it's an even number. However, once I create history with an odd number, it fails.
Reproducing the issue
Firebase SDK Version
11.3
Xcode Version
16
Installation Method
Swift Package Manager
Firebase Product(s)
App Check, Authentication, Firestore, VertexAI
Targeted Platforms
iOS
Relevant Log Output
If using Swift Package Manager, the project's Package.resolved
Expand
Package.resolved
snippet```json Replace this line with the contents of your Package.resolved. ```
If using CocoaPods, the project's Podfile.lock
Expand
Podfile.lock
snippet```yml Replace this line with the contents of your Podfile.lock! ```