Skip to content

Commit 29ee7be

Browse files
MarkDaoustcopybara-github
authored andcommitted
feat: Add Live API in-session context updates (send_context_update) and types across GenAI SDKs.
PiperOrigin-RevId: 995417746
1 parent 02db4c5 commit 29ee7be

4 files changed

Lines changed: 454 additions & 0 deletions

File tree

‎google/genai/_live_converters.py‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,98 @@ def _LiveClientContent_to_vertex(
378378
return to_object
379379

380380

381+
def _LiveClientContextUpdateTools_to_mldev(
382+
api_client: BaseApiClient,
383+
from_object: Union[dict[str, Any], object],
384+
parent_object: Optional[dict[str, Any]] = None,
385+
) -> dict[str, Any]:
386+
to_object: dict[str, Any] = {}
387+
if getv(from_object, ['tools']) is not None:
388+
setv(
389+
to_object,
390+
['tools'],
391+
[
392+
_Tool_to_mldev(t.t_tool(api_client, item), to_object)
393+
for item in t.t_tools(api_client, getv(from_object, ['tools']))
394+
],
395+
)
396+
397+
return to_object
398+
399+
400+
def _LiveClientContextUpdateTools_to_vertex(
401+
api_client: BaseApiClient,
402+
from_object: Union[dict[str, Any], object],
403+
parent_object: Optional[dict[str, Any]] = None,
404+
) -> dict[str, Any]:
405+
to_object: dict[str, Any] = {}
406+
if getv(from_object, ['tools']) is not None:
407+
setv(
408+
to_object,
409+
['tools'],
410+
[
411+
_Tool_to_vertex(t.t_tool(api_client, item), to_object)
412+
for item in t.t_tools(api_client, getv(from_object, ['tools']))
413+
],
414+
)
415+
416+
return to_object
417+
418+
419+
def _LiveClientContextUpdate_to_mldev(
420+
api_client: BaseApiClient,
421+
from_object: Union[dict[str, Any], object],
422+
parent_object: Optional[dict[str, Any]] = None,
423+
) -> dict[str, Any]:
424+
to_object: dict[str, Any] = {}
425+
if getv(from_object, ['system_instruction']) is not None:
426+
setv(
427+
to_object,
428+
['systemInstruction'],
429+
_Content_to_mldev(
430+
t.t_content(getv(from_object, ['system_instruction'])), to_object
431+
),
432+
)
433+
434+
if getv(from_object, ['tools']) is not None:
435+
setv(
436+
to_object,
437+
['tools'],
438+
_LiveClientContextUpdateTools_to_mldev(
439+
api_client, getv(from_object, ['tools']), to_object
440+
),
441+
)
442+
443+
return to_object
444+
445+
446+
def _LiveClientContextUpdate_to_vertex(
447+
api_client: BaseApiClient,
448+
from_object: Union[dict[str, Any], object],
449+
parent_object: Optional[dict[str, Any]] = None,
450+
) -> dict[str, Any]:
451+
to_object: dict[str, Any] = {}
452+
if getv(from_object, ['system_instruction']) is not None:
453+
setv(
454+
to_object,
455+
['systemInstruction'],
456+
_Content_to_vertex(
457+
t.t_content(getv(from_object, ['system_instruction'])), to_object
458+
),
459+
)
460+
461+
if getv(from_object, ['tools']) is not None:
462+
setv(
463+
to_object,
464+
['tools'],
465+
_LiveClientContextUpdateTools_to_vertex(
466+
api_client, getv(from_object, ['tools']), to_object
467+
),
468+
)
469+
470+
return to_object
471+
472+
381473
def _LiveClientMessage_to_mldev(
382474
api_client: BaseApiClient,
383475
from_object: Union[dict[str, Any], object],
@@ -408,6 +500,15 @@ def _LiveClientMessage_to_mldev(
408500
if getv(from_object, ['tool_response']) is not None:
409501
setv(to_object, ['toolResponse'], getv(from_object, ['tool_response']))
410502

503+
if getv(from_object, ['context_update']) is not None:
504+
setv(
505+
to_object,
506+
['contextUpdate'],
507+
_LiveClientContextUpdate_to_mldev(
508+
api_client, getv(from_object, ['context_update']), to_object
509+
),
510+
)
511+
411512
return to_object
412513

413514

@@ -447,6 +548,15 @@ def _LiveClientMessage_to_vertex(
447548
if getv(from_object, ['tool_response']) is not None:
448549
setv(to_object, ['toolResponse'], getv(from_object, ['tool_response']))
449550

551+
if getv(from_object, ['context_update']) is not None:
552+
setv(
553+
to_object,
554+
['contextUpdate'],
555+
_LiveClientContextUpdate_to_vertex(
556+
api_client, getv(from_object, ['context_update']), to_object
557+
),
558+
)
559+
450560
return to_object
451561

452562

‎google/genai/live.py‎

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ async def send(
124124
types.LiveClientContentOrDict,
125125
types.LiveClientRealtimeInputOrDict,
126126
types.LiveClientToolResponseOrDict,
127+
types.LiveClientContextUpdateOrDict,
127128
types.FunctionResponseOrDict,
128129
Sequence[types.FunctionResponseOrDict],
129130
]
@@ -445,6 +446,89 @@ async def send_tool_response(
445446

446447
await self._ws.send(json.dumps({'tool_response': tool_response_dict}))
447448

449+
async def send_context_update(
450+
self,
451+
*,
452+
system_instruction: Optional[types.ContentUnion] = None,
453+
tools: Optional[types.ToolListUnion] = None,
454+
context_update: Optional[types.LiveClientContextUpdateOrDict] = None,
455+
) -> None:
456+
"""Send in-session context updates to the model.
457+
458+
Updates to the context of the current session. Only fields that are set will
459+
be updated. Updates are guaranteed to be processed *in order* with the rest of
460+
the inputs.
461+
462+
Note:
463+
`system_instruction` and `tools` are part of the model preamble, so updating
464+
them invalidates the prefix cache. Update them only when strictly necessary
465+
as it might have a performance impact on model generation.
466+
467+
Args:
468+
system_instruction: Updated system instruction for the model.
469+
tools: An updated list of tools the model may use to generate subsequent
470+
responses. Pass an empty list `[]` to clear all tools.
471+
context_update: A `LiveClientContextUpdate` object or dict. Cannot be
472+
combined with `system_instruction` or `tools`.
473+
474+
Example:
475+
476+
.. code-block:: python
477+
478+
# Update instructions:
479+
await session.send_context_update(
480+
system_instruction="You are now a technical expert. Be concise."
481+
)
482+
483+
# Update tools:
484+
await session.send_context_update(tools=[my_func])
485+
486+
# Clear tools:
487+
await session.send_context_update(tools=[])
488+
"""
489+
update: types.LiveClientContextUpdate
490+
if context_update is not None:
491+
if system_instruction is not None or tools is not None:
492+
raise ValueError(
493+
'Cannot set both `context_update` and `system_instruction` or'
494+
' `tools`.'
495+
)
496+
if isinstance(context_update, types.LiveClientContextUpdate):
497+
update = context_update
498+
elif isinstance(context_update, dict):
499+
update = types.LiveClientContextUpdate.model_validate(context_update)
500+
else:
501+
raise ValueError(
502+
f'Unsupported context_update type "{type(context_update)}"'
503+
)
504+
else:
505+
if system_instruction is None and tools is None:
506+
raise ValueError(
507+
'At least one of `system_instruction`, `tools`, or `context_update`'
508+
' must be set.'
509+
)
510+
tools_wrapper: Optional[types.LiveClientContextUpdateTools] = None
511+
if tools is not None:
512+
tools_wrapper = types.LiveClientContextUpdateTools(tools=tools)
513+
514+
update = types.LiveClientContextUpdate(
515+
system_instruction=system_instruction,
516+
tools=tools_wrapper,
517+
)
518+
519+
client_message = types.LiveClientMessage(context_update=update)
520+
if self._api_client.vertexai:
521+
message_dict = live_converters._LiveClientMessage_to_vertex(
522+
self._api_client, client_message
523+
)
524+
else:
525+
message_dict = live_converters._LiveClientMessage_to_mldev(
526+
self._api_client, client_message
527+
)
528+
message_dict = _common.convert_to_dict(message_dict)
529+
message_dict = _common.encode_unserializable_types(message_dict)
530+
await self._ws.send(json.dumps(message_dict))
531+
448532
async def receive(self) -> AsyncIterator[types.LiveServerMessage]:
449533
"""Receive model responses from the server.
450534
@@ -605,6 +689,7 @@ def _parse_client_message(
605689
types.LiveClientContentOrDict,
606690
types.LiveClientRealtimeInputOrDict,
607691
types.LiveClientToolResponseOrDict,
692+
types.LiveClientContextUpdateOrDict,
608693
types.FunctionResponseOrDict,
609694
Sequence[types.FunctionResponseOrDict],
610695
]
@@ -891,6 +976,41 @@ def _parse_client_message(
891976
)
892977
)
893978

979+
elif isinstance(formatted_input, types.LiveClientContextUpdate):
980+
context_update_dict = formatted_input.model_dump(
981+
exclude_none=True, mode='json'
982+
)
983+
client_message = types.LiveClientMessageDict(
984+
context_update=types.LiveClientContextUpdateDict(
985+
system_instruction=context_update_dict.get('system_instruction'),
986+
tools=context_update_dict.get('tools'),
987+
)
988+
)
989+
elif isinstance(formatted_input, dict) and (
990+
'context_update' in formatted_input
991+
or 'system_instruction' in formatted_input
992+
or 'tools' in formatted_input
993+
):
994+
update_val = (
995+
formatted_input['context_update']
996+
if 'context_update' in formatted_input
997+
else formatted_input
998+
)
999+
if isinstance(update_val, types.LiveClientContextUpdate):
1000+
update_dict = update_val.model_dump(exclude_none=True, mode='json')
1001+
elif isinstance(update_val, dict):
1002+
update_dict = update_val
1003+
else:
1004+
raise ValueError(
1005+
f'Unsupported context_update type "{type(update_val)}"'
1006+
)
1007+
client_message = types.LiveClientMessageDict(
1008+
context_update=types.LiveClientContextUpdateDict(
1009+
system_instruction=update_dict.get('system_instruction'),
1010+
tools=update_dict.get('tools'),
1011+
)
1012+
)
1013+
8941014
else:
8951015
raise ValueError(
8961016
f'Unsupported input type "{type(input)}" or input content "{input}"'

0 commit comments

Comments
 (0)