diff --git a/.github/workflows/analyze-releases-for-adk-docs-updates.yml b/.github/workflows/analyze-releases-for-adk-docs-updates.yml index 21414ae534..d7016fb702 100644 --- a/.github/workflows/analyze-releases-for-adk-docs-updates.yml +++ b/.github/workflows/analyze-releases-for-adk-docs-updates.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.github/workflows/check-file-contents.yml b/.github/workflows/check-file-contents.yml index bb575e0f20..6c02d904c7 100644 --- a/.github/workflows/check-file-contents.yml +++ b/.github/workflows/check-file-contents.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/discussion_answering.yml b/.github/workflows/discussion_answering.yml index d9bfffc361..97116d485e 100644 --- a/.github/workflows/discussion_answering.yml +++ b/.github/workflows/discussion_answering.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 @@ -24,7 +24,7 @@ jobs: - name: Authenticate to Google Cloud id: auth - uses: 'google-github-actions/auth@v2' + uses: 'google-github-actions/auth@v3' with: credentials_json: '${{ secrets.ADK_GCP_SA_KEY }}' diff --git a/.github/workflows/isort.yml b/.github/workflows/isort.yml index b8b24da5ce..a1967b1f53 100644 --- a/.github/workflows/isort.yml +++ b/.github/workflows/isort.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/pr-triage.yml b/.github/workflows/pr-triage.yml index 55b088b505..6557820f04 100644 --- a/.github/workflows/pr-triage.yml +++ b/.github/workflows/pr-triage.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.github/workflows/pyink.yml b/.github/workflows/pyink.yml index 0822757fa0..bcd872bda8 100644 --- a/.github/workflows/pyink.yml +++ b/.github/workflows/pyink.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/python-unit-tests.yml b/.github/workflows/python-unit-tests.yml index 3fc6bd943f..a19e893469 100644 --- a/.github/workflows/python-unit-tests.yml +++ b/.github/workflows/python-unit-tests.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 6948b56459..2e74e5e51f 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 46153f413a..d19a0e9197 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.github/workflows/upload-adk-docs-to-vertex-ai-search.yml b/.github/workflows/upload-adk-docs-to-vertex-ai-search.yml index bce7598c2f..e8d94eb9dc 100644 --- a/.github/workflows/upload-adk-docs-to-vertex-ai-search.yml +++ b/.github/workflows/upload-adk-docs-to-vertex-ai-search.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Clone adk-docs repository run: git clone https://github.com/google/adk-docs.git /tmp/adk-docs @@ -28,7 +28,7 @@ jobs: - name: Authenticate to Google Cloud id: auth - uses: 'google-github-actions/auth@v2' + uses: 'google-github-actions/auth@v3' with: credentials_json: '${{ secrets.ADK_GCP_SA_KEY }}' diff --git a/contributing/samples/api_registry_agent/agent.py b/contributing/samples/api_registry_agent/agent.py index 6504822092..4a36e907a8 100644 --- a/contributing/samples/api_registry_agent/agent.py +++ b/contributing/samples/api_registry_agent/agent.py @@ -32,8 +32,19 @@ root_agent = LlmAgent( model="gemini-2.0-flash", name="bigquery_assistant", - instruction=""" -Help user access their BigQuery data via API Registry tools. + instruction=f""" +You are a helpful data analyst assistant with access to BigQuery. The project ID is: {PROJECT_ID} + +When users ask about data: +- Use the project ID {PROJECT_ID} when calling BigQuery tools. +- First, explore available datasets and tables to understand what data exists. +- Check table schemas to understand the structure before querying. +- Write clear, efficient SQL queries to answer their questions. +- Explain your findings in simple, non-technical language. + +Mandatory Requirements: +- Always use the BigQuery tools to fetch real data rather than making assumptions. +- For all BigQuery operations, use project_id: {PROJECT_ID}. """, tools=[registry_tools], ) diff --git a/src/google/adk/cli/utils/local_storage.py b/src/google/adk/cli/utils/local_storage.py index 6fb6a83ed0..12207e8070 100644 --- a/src/google/adk/cli/utils/local_storage.py +++ b/src/google/adk/cli/utils/local_storage.py @@ -34,6 +34,8 @@ logger = logging.getLogger("google_adk." + __name__) +_BUILT_IN_SESSION_SERVICE_KEY = "__adk_built_in_session_service__" + def create_local_database_session_service( *, @@ -124,6 +126,16 @@ def __init__( async def _get_service(self, app_name: str) -> BaseSessionService: async with self._service_lock: + if app_name.startswith("__"): + service = self._services.get(_BUILT_IN_SESSION_SERVICE_KEY) + if service is not None: + return service + service = create_local_database_session_service( + base_dir=self._agents_root, + ) + self._services[_BUILT_IN_SESSION_SERVICE_KEY] = service + return service + storage_name = self._app_name_to_dir.get(app_name, app_name) service = self._services.get(storage_name) if service is not None: diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index aca230bc57..57c7c93029 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -628,9 +628,12 @@ def _is_ollama_chat_provider( model: Optional[str], custom_llm_provider: Optional[str] ) -> bool: """Returns True when requests should be normalized for ollama_chat.""" - if custom_llm_provider and custom_llm_provider.lower() == "ollama_chat": + if ( + custom_llm_provider + and custom_llm_provider.strip().lower() == "ollama_chat" + ): return True - if model and model.lower().startswith("ollama_chat"): + if model and model.strip().lower().startswith("ollama_chat"): return True return False @@ -644,11 +647,24 @@ def _flatten_ollama_content( join them with newlines, and fall back to a JSON string for non-text content. If both text and non-text parts are present, only the text parts are kept. """ - if not isinstance(content, list): + if content is None or isinstance(content, str): return content + # `OpenAIMessageContent` is typed as `Iterable[...]` in LiteLLM. Some + # providers or LiteLLM versions may hand back tuples or other iterables. + if isinstance(content, dict): + try: + return json.dumps(content) + except TypeError: + return str(content) + + try: + blocks = list(content) + except TypeError: + return str(content) + text_parts = [] - for block in content: + for block in blocks: if isinstance(block, dict) and block.get("type") == "text": text_value = block.get("text") if text_value: @@ -658,9 +674,9 @@ def _flatten_ollama_content( return _NEW_LINE.join(text_parts) try: - return json.dumps(content) + return json.dumps(blocks) except TypeError: - return str(content) + return str(blocks) def _normalize_ollama_chat_messages( diff --git a/tests/unittests/cli/utils/test_local_storage.py b/tests/unittests/cli/utils/test_local_storage.py index 39bce7a58b..bb922a5838 100644 --- a/tests/unittests/cli/utils/test_local_storage.py +++ b/tests/unittests/cli/utils/test_local_storage.py @@ -72,6 +72,18 @@ async def test_per_agent_session_service_respects_app_name_alias( assert (tmp_path / folder_name / ".adk" / "session.db").exists() +@pytest.mark.asyncio +async def test_per_agent_session_service_routes_built_in_agents_to_root_dot_adk( + tmp_path: Path, +) -> None: + service = PerAgentDatabaseSessionService(agents_root=tmp_path) + + await service.create_session(app_name="__helper", user_id="user") + + assert not (tmp_path / "__helper").exists() + assert (tmp_path / ".adk" / "session.db").exists() + + def test_create_local_database_session_service_returns_sqlite( tmp_path: Path, ) -> None: diff --git a/tests/unittests/models/test_litellm.py b/tests/unittests/models/test_litellm.py index 54b0f176f6..a7fe91d0df 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -1549,6 +1549,17 @@ async def test_generate_content_async_custom_provider_flattens_content( assert "Describe this image." in message_content +def test_flatten_ollama_content_accepts_tuple_blocks(): + from google.adk.models.lite_llm import _flatten_ollama_content + + content = ( + {"type": "text", "text": "first"}, + {"type": "text", "text": "second"}, + ) + flattened = _flatten_ollama_content(content) + assert flattened == "first\nsecond" + + @pytest.mark.asyncio async def test_content_to_message_param_user_message(): content = types.Content(