Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 86 additions & 12 deletions google/genai/chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@
from . import _transformers as t
from . import types
from .models import AsyncModels, Models
from .types import Content, ContentOrDict, GenerateContentConfigOrDict, GenerateContentResponse, Part, PartUnionDict
from .types import (
ChatConfig,
Content,
ContentOrDict,
GenerateContentConfig,
GenerateContentConfigOrDict,
GenerateContentResponse,
Part,
PartUnionDict,
)


if sys.version_info >= (3, 10):
Expand Down Expand Up @@ -104,20 +113,40 @@ def _extract_curated_history(
return curated_history


def _extract_generate_content_config(
chat_config: Optional[ChatConfig] = None,
) -> Optional[GenerateContentConfig]:
"""Slices a ChatConfig instance to a GenerateContentConfig instance.
"""
if not chat_config:
return None

generate_content_config_kwargs = {
field: getattr(chat_config, field)
for field in GenerateContentConfig.model_fields
}
return GenerateContentConfig(**generate_content_config_kwargs)


class _BaseChat:
"""Base chat session."""

def __init__(
self,
*,
model: str,
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
history: list[ContentOrDict],
):
self._model = model
self._config = _extra_utils.get_usage_header(
if isinstance(config, ChatConfig):
self._config = _extra_utils.get_usage_header(
config, types.ChatConfig, usage="chat", # type: ignore[arg-type]
)
else:
self._config = _extra_utils.get_usage_header(
config, types.GenerateContentConfig, usage="chat" # type: ignore[arg-type]
)
)
content_models = []
for content in history:
if not isinstance(content, Content):
Expand Down Expand Up @@ -214,7 +243,7 @@ def __init__(
*,
modules: Models,
model: str,
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
history: list[ContentOrDict],
):
self._modules = modules
Expand All @@ -227,7 +256,7 @@ def __init__(
def send_message(
self,
message: Union[list[PartUnionDict], PartUnionDict],
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
) -> GenerateContentResponse:
"""Sends the conversation history with the additional message and returns the model's response.

Expand All @@ -254,6 +283,18 @@ def send_message(
)
input_content = t.t_content(message)
method_config = config if config else self._config

# tmp workaround before the next major version update
# extract afc config from ChatConfig and merge it into GenerateContentConfig
if isinstance(method_config, ChatConfig):
afc_config = method_config.automatic_function_calling_config
if afc_config.enable is not None:
afc_config.disable = not afc_config.enable
gc_config = _extract_generate_content_config(method_config)
gc_config.automatic_function_calling = afc_config
method_config = gc_config


method_config = _extra_utils.get_usage_header(
method_config, types.GenerateContentConfig, usage="chat" # type: ignore[arg-type]
)
Expand Down Expand Up @@ -283,7 +324,7 @@ def send_message(
def send_message_stream(
self,
message: Union[list[PartUnionDict], PartUnionDict],
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
) -> Iterator[GenerateContentResponse]:
"""Sends the conversation history with the additional message and yields the model's response in chunks.

Expand Down Expand Up @@ -315,6 +356,17 @@ def send_message_stream(
is_valid = True
chunk = None
method_config = config if config else self._config

# tmp workaround before the next major version update
# extract afc config from ChatConfig and merge it into GenerateContentConfig
if isinstance(method_config, ChatConfig):
afc_config = method_config.automatic_function_calling_config
if afc_config.enable is not None:
afc_config.disable = not afc_config.enable
gc_config = _extract_generate_content_config(method_config)
gc_config.automatic_function_calling = afc_config
method_config = gc_config

method_config = _extra_utils.get_usage_header(
method_config, types.GenerateContentConfig, usage="chat" # type: ignore[arg-type]
)
Expand Down Expand Up @@ -356,7 +408,7 @@ def create(
self,
*,
model: str,
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
history: Optional[list[ContentOrDict]] = None,
) -> Chat:
"""Creates a new chat session.
Expand Down Expand Up @@ -385,7 +437,7 @@ def __init__(
*,
modules: AsyncModels,
model: str,
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
history: list[ContentOrDict],
):
self._modules = modules
Expand All @@ -398,7 +450,7 @@ def __init__(
async def send_message(
self,
message: Union[list[PartUnionDict], PartUnionDict],
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
) -> GenerateContentResponse:
"""Sends the conversation history with the additional message and returns model's response.

Expand All @@ -424,6 +476,17 @@ async def send_message(
)
input_content = t.t_content(message)
method_config = config if config else self._config

# tmp workaround before the next major version update
# extract afc config from ChatConfig and merge it into GenerateContentConfig
if isinstance(method_config, ChatConfig):
afc_config = method_config.automatic_function_calling_config
if afc_config.enable is not None:
afc_config.disable = not afc_config.enable
gc_config = _extract_generate_content_config(method_config)
gc_config.automatic_function_calling = afc_config
method_config = gc_config

method_config = _extra_utils.get_usage_header(
method_config, types.GenerateContentConfig, usage="chat" # type: ignore[arg-type]
)
Expand Down Expand Up @@ -453,7 +516,7 @@ async def send_message(
async def send_message_stream(
self,
message: Union[list[PartUnionDict], PartUnionDict],
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
) -> AsyncIterator[GenerateContentResponse]:
"""Sends the conversation history with the additional message and yields the model's response in chunks.

Expand Down Expand Up @@ -482,6 +545,17 @@ async def send_message_stream(
input_content = t.t_content(message)

method_config = config if config else self._config

# tmp workaround before the next major version update
# extract afc config from ChatConfig and merge it into GenerateContentConfig
if isinstance(method_config, ChatConfig):
afc_config = method_config.automatic_function_calling_config
if afc_config.enable is not None:
afc_config.disable = not afc_config.enable
gc_config = _extract_generate_content_config(method_config)
gc_config.automatic_function_calling = afc_config
method_config = gc_config

method_config = _extra_utils.get_usage_header(
method_config, types.GenerateContentConfig, usage="chat" # type: ignore[arg-type]
)
Expand Down Expand Up @@ -529,7 +603,7 @@ def create(
self,
*,
model: str,
config: Optional[GenerateContentConfigOrDict] = None,
config: Optional[Union[GenerateContentConfigOrDict, ChatConfig]] = None,
history: Optional[list[ContentOrDict]] = None,
) -> AsyncChat:
"""Creates a new chat session.
Expand Down
34 changes: 34 additions & 0 deletions google/genai/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6554,6 +6554,14 @@ def generate_content(
raise errors.UnsupportedFunctionError(
'MCP sessions are not supported in synchronous methods.'
)
if (
parsed_config
and parsed_config.automatic_function_calling
and parsed_config.automatic_function_calling.enable is not None
):
parsed_config.automatic_function_calling.disable = (
not parsed_config.automatic_function_calling.enable
)
if _extra_utils.should_disable_afc(parsed_config):
return self._generate_content(
model=model, contents=contents, config=parsed_config
Expand Down Expand Up @@ -6725,6 +6733,14 @@ def generate_content_stream(
raise errors.UnsupportedFunctionError(
'MCP sessions are not supported in synchronous methods.'
)
if (
parsed_config
and parsed_config.automatic_function_calling
and parsed_config.automatic_function_calling.enable is not None
):
parsed_config.automatic_function_calling.disable = (
not parsed_config.automatic_function_calling.enable
)
if _extra_utils.should_disable_afc(parsed_config):
yield from self._generate_content_stream(
model=model, contents=contents, config=parsed_config
Expand Down Expand Up @@ -8762,6 +8778,14 @@ async def generate_content(
)
)

if (
final_parsed_config
and final_parsed_config.automatic_function_calling
and final_parsed_config.automatic_function_calling.enable is not None
):
final_parsed_config.automatic_function_calling.disable = (
not final_parsed_config.automatic_function_calling.enable
)
if _extra_utils.should_disable_afc(final_parsed_config):
return await self._generate_content(
model=model, contents=contents, config=final_parsed_config
Expand Down Expand Up @@ -9001,6 +9025,16 @@ async def stream_generator(): # type: ignore[no-untyped-def]
)
)

if (
final_parsed_config
and final_parsed_config.automatic_function_calling
and final_parsed_config.automatic_function_calling.enable
is not None
):
final_parsed_config.automatic_function_calling.disable = (
not final_parsed_config.automatic_function_calling.enable
)

if _extra_utils.should_disable_afc(final_parsed_config):
response = await self._generate_content_stream(
model=model, contents=contents, config=final_parsed_config
Expand Down
27 changes: 27 additions & 0 deletions google/genai/tests/chats/test_send_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,3 +945,30 @@ async def test_async_server_side_mcp_tools(client):
await chat.send_message(
'What is the weather in San Francisco on 02/02/2026?'
)


def test_afc_chat_config(client):

def get_weather(location: str) -> str:
return f'The weather in {location} is sunny and 70 degrees.'

chat = client.chats.create(
model='gemini-3.1-pro-preview',
config=types.ChatConfig(
tools=[get_weather],
automatic_function_calling_config=types.AutomaticFunctionCallingConfig(
enable=True
),
),
)
response = chat.send_message('What is the weather in Boston?')
history = chat.get_history()
assert len(history) == 4
assert history[0].role == 'user'
assert history[1].role == 'model'
assert history[1].parts[0].function_call.name == 'get_weather'
assert history[1].parts[0].function_call.args == {'location': 'Boston'}
assert history[2].role == 'user'
assert history[2].parts[0].function_response.name == 'get_weather'
assert history[3].role == 'model'
assert '70' in history[3].parts[0].text
33 changes: 33 additions & 0 deletions google/genai/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5592,6 +5592,15 @@ class AutomaticFunctionCallingConfig(_common.BaseModel):
GenerateContentResponse.automatic_function_calling_history.
""",
)
enable: Optional[bool] = Field(
default=None,
description="""Whether to enable automatic function calling.
If not set or set to False, will not enable automatic function calling.
If set to True, will enable automatic function calling.
NOTE: This field takes precedence over the `disable` field. the `disable`
field will be deprecated.
""",
)


class AutomaticFunctionCallingConfigDict(TypedDict, total=False):
Expand All @@ -5618,6 +5627,14 @@ class AutomaticFunctionCallingConfigDict(TypedDict, total=False):
GenerateContentResponse.automatic_function_calling_history.
"""

enable: Optional[bool]
"""Whether to enable automatic function calling.
If not set or set to False, will not enable automatic function calling.
If set to True, will enable automatic function calling.
NOTE: This field takes precedence over the `disable` field. the `disable`
field will be deprecated.
"""


AutomaticFunctionCallingConfigOrDict = Union[
AutomaticFunctionCallingConfig, AutomaticFunctionCallingConfigDict
Expand Down Expand Up @@ -22534,6 +22551,22 @@ class EmbedContentParametersDict(TypedDict, total=False):
]


class ChatConfig(GenerateContentConfig):
"""Configuration for chat.

This is a sub class of `GenerateContentConfig`, it supports all the
configurations in `GenerateContentConfig`.
"""

automatic_function_calling_config: Optional[
AutomaticFunctionCallingConfig
] = Field(
default=None,
description="""The configuration for automatic function calling.
""",
)


class UserContent(Content):
"""UserContent facilitates the creation of a Content object with a user role.

Expand Down
Loading