Skip to content

Live API session resumption handle is sent after session.receive() ends. #2527

@OleksandrChornyi2010

Description

@OleksandrChornyi2010

Environment details

  • Programming language: python
  • OS: Arch linux 64-bit
  • Language runtime version: 3.13.8
  • Package version: 2.7.0

Hello! My code is:

import asyncio
from pathlib import Path
import sys
from google import genai
from google.genai import types
from utils import Utils
from websockets.exceptions import ConnectionClosedError
import sys

key_path = Path("./gemini-api-key.txt")
if not key_path.exists():
    key_path.touch()

with open(key_path, "r") as f:
    key = f.readline().strip()
    if not key:
        print(f"Paste your API key into {key_path.resolve()}")
        sys.exit(1)

client = genai.Client(
    api_key=key,
    http_options={'api_version': 'v1beta'}
)

MODEL = "gemini-2.5-flash-native-audio-latest"

get_weather_tool = {
    "name": "getWeather",
    "description": "gets the weather (temperature in Celsius) for a requested city",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string"}
        },
        "required": ["city"]
    }
}
HANDLE_STORE = Path("./last_session_handle.txt")
tools_config = [types.Tool(function_declarations=[get_weather_tool])]


def load_saved_handle():
    if HANDLE_STORE.exists():
        return HANDLE_STORE.read_text().strip()
    return None


def save_handle(token):
    HANDLE_STORE.write_text(token)


saved_token = load_saved_handle()

CONFIG = types.LiveConnectConfig(
    response_modalities=["AUDIO", "TEXT"],
    output_audio_transcription={},
    tools=tools_config,
    session_resumption=types.SessionResumptionConfig(
        handle=saved_token
    ),
)


def get_weather(city=""):
    temp = 30
    if city == "Kyiv":
        temp = 25
    elif city == "Paris":
        temp = 34
    return temp


async def run():
    current_handle = None
    while True:
        try:
            current_config = types.LiveConnectConfig(
                response_modalities=["AUDIO"],
                output_audio_transcription={},
                tools=tools_config,
                session_resumption=types.SessionResumptionConfig(
                    handle=current_handle
                )
            )
            async with client.aio.live.connect(model=MODEL, config=current_config) as session:
                print("Connected!")

                while True:
                    text = await asyncio.to_thread(input, "\nYou: ")
                    if text.lower() == "q":
                        sys.exit(0)

                    await session.send_realtime_input(
                        text=text
                    )

                    print("Gemini: ", end="")
                    async for response in session.receive():
                        if response.tool_call:
                            print("The tool was called")
                            function_responses = []
                            for fc in response.tool_call.function_calls:
                                print(f"F to call: {fc.name} {fc.args}")
                                if fc.name == "getWeather":

                                    result = get_weather(fc.args["city"])
                                    function_response = types.FunctionResponse(
                                        id=fc.id,
                                        name=fc.name,
                                        response={"result": result}
                                    )
                                    function_responses.append(
                                        function_response)

                            await session.send_tool_response(function_responses=function_responses)

                        if response.go_away is not None:
                            # The connection will soon be terminated
                            print("The session will soon be terminated: ",
                                  response.go_away.time_left)

                        if response.server_content:
                            if response.server_content.output_transcription:
                                print(
                                    Utils.yellow(response.server_content.output_transcription.text), end="", flush=True)

                            if response.server_content.model_turn:
                                for part in response.server_content.model_turn.parts:
                                    if part.text:
                                        print(Utils.red(part.text),
                                              end="", flush=True)

                        if response.session_resumption_update and response.session_resumption_update.resumable and response.session_resumption_update.new_handle:
                            current_handle = response.session_resumption_update.new_handle
                            print(Utils.pink(
                                f"Saving normal handle: {response.session_resumption_update.new_handle}"))

                    print()
                    try:
                        iterator = session.receive().__aiter__()
                        trailing_response = await asyncio.wait_for(iterator.__anext__(), timeout=1.0)

                        if trailing_response.session_resumption_update and \
                                trailing_response.session_resumption_update.resumable and \
                                trailing_response.session_resumption_update.new_handle:

                            current_handle = trailing_response.session_resumption_update.new_handle
                            print(Utils.pink(
                                "Saved final handle (from trailing response)"))

                    except (asyncio.TimeoutError, StopAsyncIteration):
                        pass

        except Exception as e:
            print(f"\n[SYSTEM] Any error occured: {repr(e)}")
            print("[SYSTEM] Reconnecting in 2 seconds...")
            await asyncio.sleep(2)

        except ConnectionClosedError as e:
            print(f"\n[SYSTEM] Connection lost: {repr(e)}")
            print("[SYSTEM] Reconnecting in 2 seconds...")
            await asyncio.sleep(2)

if __name__ == "__main__":
    asyncio.run(run())

So every time the main async receive loop exists because of StopAsyncIteration exeption, the server sends the updated handle with the latest model answer in another async session with the updated handle.
Take a look at the output:

Connected!

You: Remember numbers 3, 4, 5
Gemini: **Confirming Number Sequence**

I've stored the number sequence: 3, 4, 5. I will make sure I do not forget. I can retrieve it on request.


Got it, I'll remember 3, 4, 5.
Saved final handle (from trailing response)

You: Now tell me the numbers
Gemini: **Recalling the Digits**

I've successfully retrieved the memorized sequence: 3, 4, and 5. I am now prepared to provide this information as requested.


The numbers I remember are 3, 4, and 5.
Saved final handle (from trailing response)

You: 

but if I comment the final handle section out, this is the behaviour:

Connected!

You: Remember numbers 3, 4, 5
Gemini: **Confirming Number Sequence**

I've stored the number sequence: 3, 4, 5. I will make sure I do not forget. I can retrieve it on request.


Got it, I'll remember the numbers 3, 4, and 5.
(No handle arrived here)
You: Tell me the numbers
Gemini: Saving normal handle: Cig5MTdjb2d1Y213ZnRoc25hZDQ5aWgzdnFybjJ4dzA2aXYyYThjNGFv (handle that arrived late after model's responce)
**Recalling Numbers Requested**

I recall the user's specific request in the prior turn: "Remember numbers 3, 4, 5." Accordingly, my primary task is to state those numbers, as requested.


The numbers you asked me to remember are 3, 4, and 5.

You: 

Can you please fix this issue?

Metadata

Metadata

Assignees

Labels

priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions