Skip to content

Multiple Function Calls in One Turn Fails to Accept Response #14022

Closed
@TiVoShane

Description

@TiVoShane

Description

When utilizing a model that can call multiple function calls in one pass (i.e. gemini-1.5-flash-002), there is no way to provide multiple responses without a failure occurring.

In my example code, when asking about one exchange rate, I return one FunctionResponse and it succeeds. However, when asking about two exchange rates, and two FunctionCalls appear in one turn, I can't send a response without getting an error. I tried sending them as [FunctionResponse1, FunctionResponse2] and also separately, as two separate responses. Each time I get the following error:

11.4.0 - [FirebaseVertexAI][I-VTX002004] Response payload: {
"error": {
"code": 400,
"message": "Please ensure that function response turn comes immediately after a function call turn. And the number of function response parts should be equal to number of function call parts of the function call turn.",
"status": "INVALID_ARGUMENT"
}
}

Reproducing the issue

import Foundation
import FirebaseVertexAI

// Creating a TestAgent() will execute the testTwoFunctionCallsStreaming function.

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-002" // gemini-1.5-pro-exp-0801", //gemini-1.5-pro", //gemini-1.5-flash",
private var chat : Chat?
// 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 testTwoFunctionCallsStreaming()
    }
}

let getExchangeRate = FunctionDeclaration(
    name: "getExchangeRate",
    description: "Get the exchange rate for currencies between countries",
    parameters: [
        "currencyFrom": .string(
            description: "The currency to convert from."
        ),
        "currencyTo": .string(
            description: "The currency to convert to."
        ),
    ]
)

func makeAPIRequest(currencyFrom: String,
                    currencyTo: String) -> JSONObject {
    // This hypothetical API returns a JSON such as:
    // {"base":"USD","rates":{"SEK": 10.99}}
    var number = 10.99
    if currencyTo != "SEK" {
        number = 5.0
    }
    return [
        "base": .string(currencyFrom),
        "rates": .object([currencyTo: .number(number)]),
    ]
}

func testTwoFunctionCallsStreaming() async {
    print("testTwoFunctionCallsStreaming")
    let model = vertex.generativeModel(
        modelName: modelName,
        generationConfig: config,
        tools: [Tool.functionDeclarations( [getExchangeRate])],
        systemInstruction: ModelContent(role: "system", parts: self.systemInstructions)
    )
    // create some fake history.
    let mc1 = ModelContent(role: "user", parts: "Hello, how are you?")
    let mc2 = ModelContent(role: "model", parts: "I'm great!  How are you?")
    let mc3 = ModelContent(role: "user", parts: "I'm good.  Thanks for asking.")
    let mc4 = ModelContent(role: "model", parts: "My pleasure.  What can I do for you today?")
    
    let history = [mc1, mc2, mc3, mc4]
    chat = model.startChat(history: history)
    
    var response = try! chat?.sendMessageStream("Hello")
    await processResult(response: response!)
    
    response = try! chat?.sendMessageStream("What can you do?")
    await processResult(response: response!)

    response = try! chat?.sendMessageStream("How much is 50 US dollars worth in Swedish krona?")
    await processResult(response: response!)

    //Get one response to match the functioncall
    var apiResponse = makeAPIRequest(currencyFrom: "USD", currencyTo: "SEK")
   

    // Send the API response back to the model so it can generate a text response that can be
    // displayed to the user.
    response = try! chat?.sendMessageStream([ModelContent(
        role: "function",
        parts: [FunctionResponsePart(
            name: "getExchangeRate",
            response: apiResponse
        )]
    )])
    
    await processResult(response: response!)

    // Now ask it a question that will cause two function calls.
    response = try! chat?.sendMessageStream("How much is 50 US dollars worth in Swedish krona and the Euro?")
    await processResult(response: response!)

    //there should be two function calls.

    apiResponse = makeAPIRequest(currencyFrom: "USD", currencyTo: "SEK")
    let functionResponse1 = ModelContent(
        role: "function",
        parts: [FunctionResponsePart(
            name: "getExchangeRate",
            response: apiResponse
        )]
    )
    
    apiResponse = makeAPIRequest(currencyFrom: "USD", currencyTo: "EUR")
    let functionResponse2 = ModelContent(
        role: "function",
        parts: [FunctionResponsePart(
            name: "getExchangeRate",
            response: apiResponse
        )]
    )
    
    //METHOD 1
    // THIS FAILS
    let result = try! chat?.sendMessageStream([functionResponse1, functionResponse2])
    await processResult(response: result!)
    //  END METHOD 1
    
    
    //METHOD 2
    // THIS FAILS TOO

/*
let result = try! chat?.sendMessageStream([functionResponse1])
await processResult(response: result!)

    let result2 = try! chat?.sendMessageStream([functionResponse2])
    await processResult(response: result2!)

*/
// END METHOD 2
print(chat?.history.debugDescription ?? "No History")
print("END testTwoFunctionCalls")

}

func processResult(response : AsyncThrowingStream<GenerateContentResponse, Error>) async {
    do {
        for try await chunk in response {
            processResponseContent(content: chunk)
        }
    } catch {
    }
}

func processResponseContent(content: GenerateContentResponse) {
  guard let candidate = content.candidates.first else {
    fatalError("No candidate.")
  }

  for part in candidate.content.parts {
    switch part {
    case let textPart as TextPart:
      print(textPart.text)
     
    case let functionCallPart as FunctionCallPart:
        print(functionCallPart)
    default:
      fatalError("Unsupported response part: \(part)")
    }
  }
}

}

private extension [FunctionResponsePart] {
func modelContent() -> [ModelContent] {
return self.map { ModelContent(role: "function", parts: [$0]) }
}
}

Firebase SDK Version

11.4.0

Xcode Version

16.1

Installation Method

Swift Package Manager

Firebase Product(s)

VertexAI

Targeted Platforms

iOS

Relevant Log Output

11.4.0 - [FirebaseVertexAI][I-VTX002003] The server responded with an error: <NSHTTPURLResponse: 0x3017c8240> { URL: https://firebasevertexai.googleapis.com/v1beta/projects/expense-tracking-59dac/locations/us-central1/publishers/google/models/gemini-1.5-flash-002:streamGenerateContent?alt=sse } { Status Code: 400, Headers {
    "Alt-Svc" =     (
        "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
    );
    "Content-Length" =     (
        295
    );
    "Content-Type" =     (
        "text/event-stream"
    );
    Date =     (
        "Mon, 04 Nov 2024 17:06:07 GMT"
    );
    Server =     (
        ESF
    );
    Vary =     (
        Origin,
        "X-Origin",
        Referer
    );
    "x-content-type-options" =     (
        nosniff
    );
    "x-frame-options" =     (
        SAMEORIGIN
    );
    "x-xss-protection" =     (
        0
    );
} }
11.4.0 - [FirebaseVertexAI][I-VTX002004] Response payload: {
  "error": {
    "code": 400,
    "message": "Please ensure that function response turn comes immediately after a function call turn. And the number of function response parts should be equal to number of function call parts of the function call turn.",
    "status": "INVALID_ARGUMENT"
  }
}

If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet
Replace this line with the contents of your Package.resolved.

If using CocoaPods, the project's Podfile.lock

Expand Podfile.lock snippet
Replace this line with the contents of your Podfile.lock!

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions