Skip to content

Commit 1fbcf1d

Browse files
authored
Merge branch 'main' into fix/support-opentelemetry-1.43.0
2 parents 512ef2f + f1a0a14 commit 1fbcf1d

57 files changed

Lines changed: 2953 additions & 302 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/analyze-releases-for-adk-docs-updates.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ jobs:
4545

4646
steps:
4747
- name: Checkout repository
48-
uses: actions/checkout@v6
48+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
4949

5050
- name: Set up Python
51-
uses: actions/setup-python@v6
51+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
5252
with:
5353
python-version: '3.11'
5454

5555
- name: Load adk-bot SSH Private Key
56-
uses: webfactory/ssh-agent@v0.9.1
56+
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
5757
with:
5858
ssh-private-key: ${{ secrets.ADK_BOT_SSH_PRIVATE_KEY }}
5959

@@ -64,7 +64,7 @@ jobs:
6464
6565
- name: Restore session DB from cache
6666
if: ${{ github.event.inputs.resume == 'true' }}
67-
uses: actions/cache/restore@v4
67+
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
6868
with:
6969
path: contributing/samples/adk_team/adk_documentation/adk_release_analyzer/sessions.db
7070
key: analyzer-session-db-${{ github.run_id }}-${{ github.run_attempt }}
@@ -102,7 +102,7 @@ jobs:
102102
103103
- name: Save session DB to cache
104104
if: always()
105-
uses: actions/cache/save@v4
105+
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
106106
with:
107107
path: contributing/samples/adk_team/adk_documentation/adk_release_analyzer/sessions.db
108108
key: analyzer-session-db-${{ github.run_id }}-${{ github.run_attempt }}

.github/workflows/release-finalize.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
echo "manifest_file=.github/.release-please-manifest.json" >> $GITHUB_OUTPUT
6060
fi
6161
62-
- uses: actions/checkout@v6
62+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
6363
if: steps.check.outputs.is_release_pr == 'true'
6464
with:
6565
ref: ${{ github.event.pull_request.base.ref }}

.github/workflows/release-update-adk-web.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636

3737
steps:
3838
- name: Checkout repository
39-
uses: actions/checkout@v6
39+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
4040
with:
4141
persist-credentials: false
4242

@@ -78,7 +78,7 @@ jobs:
7878
echo "email=$(echo "$USER_JSON" | jq -r '.id')+$(echo "$USER_JSON" | jq -r '.login')@users.noreply.github.com" >> $GITHUB_OUTPUT
7979
8080
- name: Create Pull Request
81-
uses: peter-evans/create-pull-request@v6
81+
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
8282
with:
8383
token: ${{ secrets.RELEASE_PAT }}
8484
commit-message: "Update compiled adk web files from ${{ github.event.inputs.adk_web_repo }}@${{ github.event.inputs.adk_web_tag || 'latest' }}"

src/google/adk/agents/_managed_agent.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
from ..utils.content_utils import to_user_content
5252
from ..utils.context_utils import Aclosing
5353
from ..utils.env_utils import is_enterprise_mode_enabled
54+
from ..utils.instructions_utils import inject_session_state
55+
from ..utils.instructions_utils import InstructionProvider
5456
from .base_agent import BaseAgent
5557
from .context import Context
5658
from .invocation_context import InvocationContext
@@ -144,6 +146,16 @@ class ManagedAgent(BaseAgent):
144146
agent_config: Optional[CreateAgentInteractionAgentConfigParam] = None
145147
"""Runtime configuration passed to interactions.create."""
146148

149+
instruction: Union[str, InstructionProvider] = ''
150+
"""The system instruction sent to the Managed Agent.
151+
152+
A plain string may embed ``{var}``, ``{artifact.name}``, or ``{var?}``
153+
placeholders that are resolved from session state / artifacts at request time
154+
(see ``inject_session_state``). An ``InstructionProvider`` callable is invoked
155+
with a ``ReadonlyContext`` and bypasses placeholder injection (it manages
156+
state itself). Empty by default, in which case no system instruction is sent.
157+
"""
158+
147159
tools: list[
148160
Union[types.Tool, BaseTool, Callable[..., Any], RemoteMcpServer]
149161
] = Field(default_factory=list)
@@ -197,6 +209,29 @@ def api_client(self) -> Client:
197209
)
198210
return self._api_client
199211

212+
async def canonical_instruction(
213+
self, ctx: ReadonlyContext
214+
) -> tuple[str, bool]:
215+
"""Resolves ``self.instruction`` for the current context.
216+
217+
Mirrors ``LlmAgent.canonical_instruction``.
218+
219+
Args:
220+
ctx: The read-only context used to resolve an InstructionProvider.
221+
222+
Returns:
223+
A tuple of (instruction, bypass_state_injection).
224+
``bypass_state_injection``
225+
is True when the instruction came from an ``InstructionProvider`` callable
226+
(which manages state itself), False for a plain string.
227+
"""
228+
if isinstance(self.instruction, str):
229+
return self.instruction, False
230+
instruction = self.instruction(ctx)
231+
if inspect.isawaitable(instruction):
232+
instruction = await instruction
233+
return instruction, True
234+
200235
async def _resolve_backend_tools(
201236
self, ctx: InvocationContext
202237
) -> list[ToolParam]:
@@ -360,6 +395,15 @@ async def _run_async_impl(
360395
)
361396
interaction_tools = await self._resolve_backend_tools(ctx)
362397

398+
raw_si, bypass_state_injection = await self.canonical_instruction(
399+
ReadonlyContext(ctx)
400+
)
401+
system_instruction = raw_si
402+
if not bypass_state_injection:
403+
system_instruction = await inject_session_state(
404+
raw_si, ReadonlyContext(ctx)
405+
)
406+
363407
create_kwargs: dict[str, Any] = {
364408
'agent': self.agent_id,
365409
'input': input_steps,
@@ -377,6 +421,8 @@ async def _run_async_impl(
377421
create_kwargs['agent_config'] = self.agent_config
378422
if prev_interaction_id:
379423
create_kwargs['previous_interaction_id'] = prev_interaction_id
424+
if system_instruction:
425+
create_kwargs['system_instruction'] = system_instruction
380426

381427
# Request-time header merge, parity with google_llm.generate_content_async:
382428
# combine any RunConfig headers with ADK tracking headers, non-destructively.
@@ -402,7 +448,7 @@ async def _run_async_impl(
402448
build_interactions_request_log(
403449
model=self.agent_id,
404450
input_steps=input_steps,
405-
system_instruction=None,
451+
system_instruction=system_instruction or None,
406452
tools=interaction_tools if interaction_tools else None,
407453
generation_config=None,
408454
previous_interaction_id=prev_interaction_id,

src/google/adk/agents/langgraph_agent.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations
1616

17+
from typing import Any
1718
from typing import AsyncGenerator
1819
from typing import Union
1920

@@ -23,7 +24,7 @@
2324
from langchain_core.messages import HumanMessage
2425
from langchain_core.messages import SystemMessage
2526
from langchain_core.runnables.config import RunnableConfig
26-
from langgraph.graph.graph import CompiledGraph
27+
from langgraph.graph.state import CompiledStateGraph
2728
from pydantic import ConfigDict
2829
from typing_extensions import override
2930

@@ -53,14 +54,20 @@ def _get_last_human_messages(
5354

5455

5556
class LangGraphAgent(BaseAgent):
56-
"""Currently a concept implementation, supports single and multi-turn."""
57+
"""Adapts a compiled LangGraph state graph for single or multi-turn use.
58+
59+
When using a persistent checkpointer, set ``LANGGRAPH_STRICT_MSGPACK=true``
60+
before importing LangGraph and compiling the graph. LangGraph's patched
61+
releases provide schema-derived checkpoint allowlisting, but do not enable
62+
strict deserialization by default.
63+
"""
5764

5865
model_config = ConfigDict(
5966
arbitrary_types_allowed=True,
6067
)
6168
"""The pydantic model config."""
6269

63-
graph: CompiledGraph
70+
graph: CompiledStateGraph
6471

6572
instruction: str = ''
6673

@@ -73,13 +80,16 @@ async def _run_async_impl(
7380
# Needed for langgraph checkpointer (for subsequent invocations; multi-turn)
7481
config: RunnableConfig = {'configurable': {'thread_id': ctx.session.id}}
7582

76-
# Add instruction as SystemMessage if graph state is empty
77-
current_graph_state = self.graph.get_state(config)
78-
graph_messages = (
79-
current_graph_state.values.get('messages', [])
80-
if current_graph_state.values
81-
else []
82-
)
83+
# Add instruction as SystemMessage if graph state is empty. State lookup is
84+
# only valid when the compiled graph has a checkpointer.
85+
graph_messages: list[Any] = []
86+
if self.graph.checkpointer:
87+
current_graph_state = await self.graph.aget_state(config)
88+
graph_messages = (
89+
current_graph_state.values.get('messages', [])
90+
if current_graph_state.values
91+
else []
92+
)
8393
messages: list[BaseMessage] = (
8494
[SystemMessage(content=self.instruction)]
8595
if self.instruction and not graph_messages
@@ -89,7 +99,7 @@ async def _run_async_impl(
8999
messages += self._get_messages(ctx.session.events)
90100

91101
# Use the Runnable
92-
final_state = self.graph.invoke({'messages': messages}, config)
102+
final_state = await self.graph.ainvoke({'messages': messages}, config)
93103
result = final_state['messages'][-1].content
94104

95105
result_event = Event(

src/google/adk/agents/live_request_queue.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
import asyncio
18+
from typing import Any
1819
from typing import Optional
1920

2021
from google.genai import types
@@ -32,32 +33,41 @@ class LiveRequest(BaseModel):
3233
"""If set, send the content to the model in turn-by-turn mode.
3334
3435
When multiple fields are set, they are processed by priority (highest first):
35-
activity_start > activity_end > blob > content.
36+
activity_start > activity_end > blob > content. state_delta, if set, is always
37+
applied regardless of the other fields.
3638
"""
3739
blob: Optional[types.Blob] = None
3840
"""If set, send the blob to the model in realtime mode.
3941
4042
When multiple fields are set, they are processed by priority (highest first):
41-
activity_start > activity_end > blob > content.
43+
activity_start > activity_end > blob > content. state_delta, if set, is always
44+
applied regardless of the other fields.
4245
"""
4346
activity_start: Optional[types.ActivityStart] = None
4447
"""If set, signal the start of user activity to the model.
4548
4649
When multiple fields are set, they are processed by priority (highest first):
47-
activity_start > activity_end > blob > content.
50+
activity_start > activity_end > blob > content. state_delta, if set, is always
51+
applied regardless of the other fields.
4852
"""
4953
activity_end: Optional[types.ActivityEnd] = None
5054
"""If set, signal the end of user activity to the model.
5155
5256
When multiple fields are set, they are processed by priority (highest first):
53-
activity_start > activity_end > blob > content.
57+
activity_start > activity_end > blob > content. state_delta, if set, is always
58+
applied regardless of the other fields.
5459
"""
5560
close: bool = False
5661
"""If set, close the queue. queue.shutdown() is only supported in Python 3.13+."""
5762

5863
partial: bool = False
5964
"""If set, the content is a partial turn update that does not complete the current model turn."""
6065

66+
state_delta: Optional[dict[str, Any]] = None
67+
"""If set, these state changes are applied to the session, so they take
68+
effect even when the request carries no content or a partial/
69+
function-response turn."""
70+
6171

6272
class LiveRequestQueue:
6373
"""Queue used to send LiveRequest in a live(bidirectional streaming) way."""

src/google/adk/agents/llm_agent.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from ..utils._schema_utils import SchemaType
6060
from ..utils._schema_utils import validate_schema
6161
from ..utils.context_utils import Aclosing
62+
from ..utils.instructions_utils import InstructionProvider as InstructionProvider
6263
from .base_agent import BaseAgent
6364
from .base_agent import BaseAgentState
6465
from .base_agent_config import BaseAgentConfig as BaseAgentConfig
@@ -130,9 +131,6 @@
130131
list[_SingleOnToolErrorCallback],
131132
]
132133

133-
InstructionProvider: TypeAlias = Callable[
134-
[ReadonlyContext], Union[str, Awaitable[str]]
135-
]
136134
ToolUnion: TypeAlias = Union[Callable, BaseTool, BaseToolset]
137135

138136

src/google/adk/apps/_configs.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ class EventsCompactionConfig(BaseModel):
5858
summarizer: Optional[BaseEventsSummarizer] = None
5959
"""The event summarizer to use for compaction."""
6060

61-
compaction_interval: int
61+
compaction_interval: Optional[int] = Field(default=None, gt=0)
6262
"""The number of *new* user-initiated invocations that, once
6363
fully represented in the session's events, will trigger a compaction."""
6464

65-
overlap_size: int
65+
overlap_size: Optional[int] = Field(default=None, ge=0)
6666
"""The number of preceding invocations to include from the
6767
end of the last compacted range. This creates an overlap between consecutive
6868
compacted summaries, maintaining context."""
@@ -85,11 +85,22 @@ class EventsCompactionConfig(BaseModel):
8585
"""
8686

8787
@model_validator(mode="after")
88-
def _validate_token_params(self) -> EventsCompactionConfig:
88+
def _validate_trigger_params(self) -> EventsCompactionConfig:
8989
token_threshold_set = self.token_threshold is not None
9090
retention_size_set = self.event_retention_size is not None
9191
if token_threshold_set != retention_size_set:
9292
raise ValueError(
9393
"token_threshold and event_retention_size must be set together."
9494
)
95+
compaction_interval_set = self.compaction_interval is not None
96+
overlap_size_set = self.overlap_size is not None
97+
if compaction_interval_set != overlap_size_set:
98+
raise ValueError(
99+
"compaction_interval and overlap_size must be set together."
100+
)
101+
if not (token_threshold_set or compaction_interval_set):
102+
raise ValueError(
103+
"At least one compaction trigger must be configured: the"
104+
" token-threshold pair or the sliding-window pair."
105+
)
95106
return self

src/google/adk/auth/auth_handler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ def generate_auth_uri(
215215
}
216216
if auth_credential.oauth2.audience:
217217
params["audience"] = auth_credential.oauth2.audience
218+
if auth_credential.oauth2.nonce:
219+
params["nonce"] = auth_credential.oauth2.nonce
218220

219221
# If using PKCE with S256, ensure a code_verifier exists.
220222
# If not provided in the credential, generate a cryptographically secure

0 commit comments

Comments
 (0)