Google gen ai fix for empty chat log messages (#136019) (#140315)

* Google gen ai fix for empty chat log messages (#136019)

* Google gen ai test for empty chat history fields (#136019)
This commit is contained in:
Mirko Liebender 2025-03-24 06:27:35 +01:00 committed by GitHub
parent 6a7fa3769d
commit d3b8dbb76c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 2 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import codecs import codecs
from collections.abc import Callable from collections.abc import Callable
from dataclasses import replace
from typing import Any, Literal, cast from typing import Any, Literal, cast
from google.genai.errors import APIError from google.genai.errors import APIError
@ -333,6 +334,14 @@ class GoogleGenerativeAIConversationEntity(
tool_results.append(chat_content) tool_results.append(chat_content)
continue continue
if (
not isinstance(chat_content, conversation.ToolResultContent)
and chat_content.content == ""
):
# Skipping is not possible since the number of function calls need to match the number of function responses
# and skipping one would mean removing the other and hence this would prevent a proper chat log
chat_content = replace(chat_content, content=" ")
if tool_results: if tool_results:
messages.append(_create_google_tool_response_content(tool_results)) messages.append(_create_google_tool_response_content(tool_results))
tool_results.clear() tool_results.clear()

View File

@ -10,7 +10,7 @@ from syrupy.assertion import SnapshotAssertion
import voluptuous as vol import voluptuous as vol
from homeassistant.components import conversation from homeassistant.components import conversation
from homeassistant.components.conversation import trace from homeassistant.components.conversation import UserContent, async_get_chat_log, trace
from homeassistant.components.google_generative_ai_conversation.conversation import ( from homeassistant.components.google_generative_ai_conversation.conversation import (
_escape_decode, _escape_decode,
_format_schema, _format_schema,
@ -18,7 +18,7 @@ from homeassistant.components.google_generative_ai_conversation.conversation imp
from homeassistant.const import CONF_LLM_HASS_API from homeassistant.const import CONF_LLM_HASS_API
from homeassistant.core import Context, HomeAssistant from homeassistant.core import Context, HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, llm from homeassistant.helpers import chat_session, intent, llm
from . import CLIENT_ERROR_500 from . import CLIENT_ERROR_500
@ -693,3 +693,37 @@ async def test_escape_decode() -> None:
async def test_format_schema(openapi, genai_schema) -> None: async def test_format_schema(openapi, genai_schema) -> None:
"""Test _format_schema.""" """Test _format_schema."""
assert _format_schema(openapi) == genai_schema assert _format_schema(openapi) == genai_schema
@pytest.mark.usefixtures("mock_init_component")
async def test_empty_content_in_chat_history(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Tests that in case of an empty entry in the chat history the google API will receive an injected space sign instead."""
with (
patch("google.genai.chats.AsyncChats.create") as mock_create,
chat_session.async_get_chat_session(hass) as session,
async_get_chat_log(hass, session) as chat_log,
):
mock_chat = AsyncMock()
mock_create.return_value.send_message = mock_chat
# Chat preparation with two inputs, one being an empty string
first_input = "First request"
second_input = ""
chat_log.async_add_user_content(UserContent(first_input))
chat_log.async_add_user_content(UserContent(second_input))
await conversation.async_converse(
hass,
"Second request",
session.conversation_id,
Context(),
agent_id="conversation.google_generative_ai_conversation",
)
_, kwargs = mock_create.call_args
actual_history = kwargs.get("history")
assert actual_history[0].parts[0].text == first_input
assert actual_history[1].parts[0].text == " "