Skip to content

Commit 016b19e

Browse files
Merge branch 'main' into fix-windows-click-glob-expansion
2 parents 8051251 + f3529e9 commit 016b19e

7 files changed

Lines changed: 405 additions & 134 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: "ADK Issue Tracker Maintenance"
16+
17+
on:
18+
# Daily background cleanup at 6:00 AM UTC (10 PM PST)
19+
schedule:
20+
- cron: '0 6 * * *'
21+
22+
# Allows manual triggering from the Actions console
23+
workflow_dispatch:
24+
inputs:
25+
run_spam_monitor:
26+
description: 'Run Issue Monitoring Agent (Spam Sweep)'
27+
type: boolean
28+
default: true
29+
full_scan:
30+
description: 'For Issue Monitoring: Run an Initial Full Scan of ALL open issues'
31+
type: boolean
32+
default: false
33+
run_stale_auditor:
34+
description: 'Run Stale Issue Auditor Agent'
35+
type: boolean
36+
default: true
37+
38+
permissions:
39+
issues: write
40+
contents: read
41+
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
44+
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
45+
OWNER: ${{ github.repository_owner }}
46+
REPO: adk-python
47+
CONCURRENCY_LIMIT: 3
48+
LLM_MODEL_NAME: "gemini-3.5-flash"
49+
PYTHONPATH: contributing/samples/adk_team
50+
51+
jobs:
52+
# 1. Sweep for spam, advertising, and invalid issues
53+
sweep-spam:
54+
if: |
55+
github.repository == 'google/adk-python' &&
56+
(github.event_name == 'schedule' || github.event.inputs.run_spam_monitor == 'true')
57+
runs-on: ubuntu-latest
58+
timeout-minutes: 120
59+
steps:
60+
- name: Checkout repository
61+
uses: actions/checkout@v6
62+
63+
- name: Set up Python
64+
uses: actions/setup-python@v6
65+
with:
66+
python-version: '3.11'
67+
68+
- name: Install dependencies
69+
run: |
70+
python -m pip install --upgrade pip
71+
pip install requests google-adk python-dotenv
72+
73+
- name: Run Issue Monitoring Agent (Spam Sweep)
74+
env:
75+
INITIAL_FULL_SCAN: ${{ github.event.inputs.full_scan == 'true' }}
76+
run: python -m adk_issue_monitoring_agent.main
77+
78+
# 2. Audit inactive issues and nudge/close them
79+
audit-stale:
80+
if: |
81+
github.repository == 'google/adk-python' &&
82+
(github.event_name == 'schedule' || github.event.inputs.run_stale_auditor == 'true')
83+
runs-on: ubuntu-latest
84+
timeout-minutes: 60
85+
steps:
86+
- name: Checkout repository
87+
uses: actions/checkout@v6
88+
89+
- name: Set up Python
90+
uses: actions/setup-python@v6
91+
with:
92+
python-version: '3.11'
93+
94+
- name: Install dependencies
95+
run: |
96+
python -m pip install --upgrade pip
97+
pip install requests google-adk python-dateutil
98+
99+
- name: Run Stale Auditor Agent
100+
run: python -m adk_stale_agent.main

.github/workflows/issue-monitor.yml

Lines changed: 0 additions & 64 deletions
This file was deleted.

.github/workflows/stale-bot.yml

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/google/adk/agents/llm_agent.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,9 +520,13 @@ async def _run_async_impl(
520520
return
521521

522522
should_pause = False
523+
output_accumulator = ''
523524
async with Aclosing(self._llm_flow.run_async(ctx)) as agen:
524525
async for event in agen:
525526
self.__maybe_save_output_to_state(event)
527+
output_accumulator = self.__maybe_accumulate_streaming_output(
528+
event, output_accumulator
529+
)
526530
yield event
527531
if ctx.should_pause_invocation(event):
528532
# Do not pause immediately, wait until the long-running tool call is
@@ -544,9 +548,13 @@ async def _run_async_impl(
544548
async def _run_live_impl(
545549
self, ctx: InvocationContext
546550
) -> AsyncGenerator[Event, None]:
551+
output_accumulator = ''
547552
async with Aclosing(self._llm_flow.run_live(ctx)) as agen:
548553
async for event in agen:
549554
self.__maybe_save_output_to_state(event)
555+
output_accumulator = self.__maybe_accumulate_streaming_output(
556+
event, output_accumulator
557+
)
550558
yield event
551559
if ctx.end_invocation:
552560
return
@@ -962,6 +970,47 @@ def __maybe_save_output_to_state(self, event: Event):
962970
result = validate_schema(self.output_schema, result)
963971
event.actions.state_delta[self.output_key] = result
964972

973+
def __maybe_accumulate_streaming_output(
974+
self, event: Event, accumulator: str
975+
) -> str:
976+
"""Accumulates output_key text across a streaming model turn.
977+
978+
Streaming with tool calls produces non-partial events that carry text
979+
alongside a function_call. is_final_response() rejects those, so
980+
__maybe_save_output_to_state skips them and the text on those events
981+
is dropped from output_key. Accumulate every non-partial text-bearing
982+
event from this agent across the model turn so the segments survive
983+
in session state. See issue #5590.
984+
985+
No-op when accumulation doesn't apply (different author, no
986+
output_key, output_schema set, partial event, no content, no text).
987+
For applicable events, appends the event's text to ``accumulator``
988+
and writes the running value to state_delta[output_key], overwriting
989+
any value __maybe_save_output_to_state set on the same event.
990+
Returns the new accumulator value.
991+
"""
992+
if (
993+
not self.output_key
994+
or self.output_schema
995+
or event.author != self.name
996+
or event.partial
997+
or not event.content
998+
or not event.content.parts
999+
):
1000+
return accumulator
1001+
1002+
text = ''.join(
1003+
part.text
1004+
for part in event.content.parts
1005+
if part.text and not part.thought
1006+
)
1007+
if not text:
1008+
return accumulator
1009+
1010+
accumulator += text
1011+
event.actions.state_delta[self.output_key] = accumulator
1012+
return accumulator
1013+
9651014
@model_validator(mode='after')
9661015
def __model_validator_after(self) -> LlmAgent:
9671016
return self

src/google/adk/memory/vertex_ai_memory_bank_service.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -541,18 +541,36 @@ async def search_memory(self, *, app_name: str, user_id: str, query: str):
541541
logger.info('Search memory response received.')
542542

543543
memory_events: list[MemoryEntry] = []
544-
async for retrieved_memory in retrieved_memories_iterator:
545-
# TODO: add more complex error handling
546-
logger.debug('Retrieved memory: %s', retrieved_memory)
547-
memory_events.append(
548-
MemoryEntry(
549-
author='user',
550-
content=types.Content(
551-
parts=[types.Part(text=retrieved_memory.memory.fact)],
552-
role='user',
553-
),
554-
timestamp=retrieved_memory.memory.update_time.isoformat(),
544+
try:
545+
async for retrieved_memory in retrieved_memories_iterator:
546+
try:
547+
memory = retrieved_memory.memory
548+
if memory is None:
549+
logger.warning('Skipping memory entry with missing memory object.')
550+
continue
551+
fact = memory.fact
552+
if not fact:
553+
logger.warning('Skipping memory entry with empty or missing fact.')
554+
continue
555+
update_time = memory.update_time
556+
memory_events.append(
557+
MemoryEntry(
558+
author='user',
559+
content=types.Content(
560+
parts=[types.Part(text=fact)],
561+
role='user',
562+
),
563+
timestamp=update_time.isoformat() if update_time else None,
564+
)
565+
)
566+
except AttributeError:
567+
logger.warning(
568+
'Skipping malformed memory entry: %s', retrieved_memory
555569
)
570+
except Exception:
571+
logger.exception(
572+
'Error while iterating memory results. Returning %d partial results.',
573+
len(memory_events),
556574
)
557575
return SearchMemoryResponse(memories=memory_events)
558576

0 commit comments

Comments
 (0)