Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# ==========================================
# Enables Vertex AI backend for Gemini models instead of Google AI Studio
GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT="your-project-id"
GOOGLE_CLOUD_LOCATION="us-central1"
GOOGLE_CLOUD_PROJECT=<TODO: update-this-value>
GOOGLE_CLOUD_LOCATION="us-east1"

# ==========================================
# Application Mode
Expand All @@ -16,7 +16,7 @@ USE_MOCK_API="True"
# ADK Deployment Settings
# ==========================================
# Staging bucket used to export the Agent runtime artifacts
STAGING_BUCKET="gs://your-staging-bucket"
STAGING_BUCKET=<TODO: update-this-value>
# Display name of the target agent on Vertex AI
AGENT_DISPLAY_NAME="document-analyzer-agent"

Expand All @@ -26,23 +26,23 @@ AGENT_DISPLAY_NAME="document-analyzer-agent"
# If your document API requires OAuth2 Authentication, provide the Google Secret
# IMPORTANT: These are the NAMES of the secrets in Secret Manager, NOT the values.
# The code will fetch the actual content using the service account identity.
CLIENT_ID="prod-api-client-id"
CLIENT_SECRET="prod-api-client-secret"
CLIENT_ID=<TODO: update-this-value>
CLIENT_SECRET=<TODO: update-this-value>

# The endpoint used to request the authentication bearer token (if necessary)
URL_TOKEN_API_URL="https://api.example.com/token"
URL_TOKEN_API_URL=<TODO: update-this-value>

# Base endpoint to fetch the JSON array containing the document URLs.
# You can embed '{collection_id}' dynamically into the path if needed.
DOCUMENT_API_BASE_URL="https://api.example.com/v1/documents/{collection_id}/list"
DOCUMENT_API_BASE_URL=<TODO: update-this-value>

# ==========================================
# Under-the-hood Models
# ==========================================
# The multimodal model used internally for scanning individual documents (PDFs, Images)
MODEL_NAME_DOC_PROCESSING="gemini-2.5-flash"
MODEL_NAME_DOC_PROCESSING="gemini-3.5-flash"
# The reasoning model orchestrating the conversation flow
MODEL_NAME_AGENT="gemini-2.5-flash"
MODEL_NAME_AGENT="gemini-3.5-flash"

# ==========================================
# Advanced Optimization Settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Would you like me to continue reading the older documents?
2. **Clone the repository:**
```bash
git clone https://github.com/google/adk-samples.git
cd adk-samples/python/agents/high-volume-document-analyzer
cd adk-samples/contrib/python/high-volume-document-analyzer
# Install the package and dependencies.
uv sync

Expand All @@ -96,7 +96,7 @@ Would you like me to continue reading the older documents?
Edit the `.env` file with your GCP project details
```env
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1
GOOGLE_CLOUD_LOCATION=us-east1
```

Depending on your deployment, you may also need to configure the following variables in your `.env` file:
Expand Down Expand Up @@ -261,10 +261,10 @@ Use the [Google Agents CLI](https://github.com/google/agents-cli) to create a pr
uvx google-agents-cli setup
```

**Create the project from this sample** (run from the root of the `adk-samples` repository, replace `my-document-analyzer` with your project name):
**Create the project from this recipe** (run from the root of the `adk-samples` repository, replace `my-document-analyzer` with your project name):

```bash
agents-cli create my-document-analyzer -a local@python/agents/high-volume-document-analyzer
agents-cli create my-document-analyzer -a local@contrib/python/high-volume-document-analyzer
```

The Google Agents CLI will prompt you to select deployment options and provides additional production-ready features including automated CI/CD deployment scripts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

load_dotenv(override=True)
PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT")
LOCATION = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
LOCATION = os.getenv("GOOGLE_CLOUD_LOCATION")
STAGING_BUCKET = os.getenv("STAGING_BUCKET")

if not STAGING_BUCKET:
Expand Down Expand Up @@ -64,8 +64,9 @@
"high_volume_document_analyzer/tools/process_toolset.py",
],
"requirements": [
"google-adk>=1.28.0",
"google-adk>=1.31.0",
"google-cloud-aiplatform[adk,agent-engines]>=1.93.0",
"google-cloud-secret-manager>=2.16.0",
"opentelemetry-instrumentation-google-genai==0.4b0",
"python-dotenv>=1.0.1",
"reportlab==4.2.0",
Expand All @@ -86,10 +87,8 @@
"CLIENT_SECRET": os.getenv("CLIENT_SECRET", ""),
"URL_TOKEN_API_URL": os.getenv("URL_TOKEN_API_URL", ""),
"DOCUMENT_API_BASE_URL": os.getenv("DOCUMENT_API_BASE_URL", ""),
"MODEL_NAME_DOC_PROCESSING": os.getenv(
"MODEL_NAME_DOC_PROCESSING", "gemini-2.5-flash"
),
"MODEL_NAME_AGENT": os.getenv("MODEL_NAME_AGENT", "gemini-2.5-flash"),
"MODEL_NAME_DOC_PROCESSING": os.getenv("MODEL_NAME_DOC_PROCESSING"),
"MODEL_NAME_AGENT": os.getenv("MODEL_NAME_AGENT"),
"BATCH_SIZE": os.getenv("BATCH_SIZE", "10"),
"MAX_CONCURRENT_DOWNLOADS": os.getenv("MAX_CONCURRENT_DOWNLOADS", "20"),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""High-Volume Document Analyzer Agent: query and synthesize information from documents."""

import os
from pathlib import Path

import google.auth
from dotenv import load_dotenv

# Environment bootstrap for the whole package. Real values come from .env
# (gitignored) or the ambient environment. The committed .env.example is the
# fallback so the unit tests and the runnability test import cleanly with no
# .env present. override=False throughout, so a real environment variable wins.
_ROOT = Path(__file__).resolve().parent.parent
load_dotenv(_ROOT / ".env", override=False)
load_dotenv(_ROOT / ".env.example", override=False)

try:
_, project_id = google.auth.default()
if project_id and (
not os.environ.get("GOOGLE_CLOUD_PROJECT")
or os.environ.get("GOOGLE_CLOUD_PROJECT", "").startswith("<")
):
os.environ["GOOGLE_CLOUD_PROJECT"] = project_id
except Exception:
pass

os.environ.setdefault("GOOGLE_CLOUD_LOCATION", "global")
os.environ.setdefault("GOOGLE_GENAI_USE_VERTEXAI", "True")

from . import agent # noqa: E402 -- must come after load_dotenv()

__all__ = ["agent"]
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,17 @@

import os

from dotenv import load_dotenv
from google.adk.agents import LlmAgent

from high_volume_document_analyzer.prompt import ROOT_AGENT_INSTRUCTION
from high_volume_document_analyzer.tools.document_toolset import (
analyze_document_next_chunk,
)

load_dotenv()

root_agent = LlmAgent(
name="document_analyzer_agent",
description="Agent that analyzes document collections in chunks to answer user questions.",
model=os.getenv("MODEL_NAME_AGENT", "gemini-2.5-flash"),
model=os.getenv("MODEL_NAME_AGENT"),
instruction=ROOT_AGENT_INSTRUCTION,
tools=[analyze_document_next_chunk],
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
)

CHUNK_SIZE = int(os.getenv("BATCH_SIZE", "10"))
MODEL_NAME = os.getenv("MODEL_NAME_DOC_PROCESSING", "gemini-2.5-flash")
LOCATION = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
MODEL_NAME = os.getenv("MODEL_NAME_DOC_PROCESSING")
LOCATION = os.getenv("GOOGLE_CLOUD_LOCATION")

_MODEL_INSTANCE: GenerativeModel | None = None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@

import aiohttp
import google.auth
from dotenv import load_dotenv
from google.cloud import secretmanager

load_dotenv()

logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
Expand Down Expand Up @@ -128,11 +125,10 @@ def get_credentials() -> tuple[str | None, str | None]:


async def get_auth_token_async() -> str:
"""
Obtains access token asynchronously.
"""Obtains access token asynchronously.

Avoids repeated calls to the authentication server.
"""
global _TOKEN_CACHE
current_time = time.time()

if _TOKEN_CACHE["access_token"] and current_time < (
Expand Down
33 changes: 33 additions & 0 deletions contrib/python/high-volume-document-analyzer/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type: "standalone" # Options: [standalone | module]
status: "active" # Options: [active | inactive]
language: "python" # Options: [python | java | go | kotlin | typescript]
description: "The High-Volume Document Analyzer Agent queries and synthesizes information from a large corpus of internal documents (legal statutes, process manuals, etc.)"

# deployable: true # (optional) one-command deploy, no manual steps; omit if false (default)
# large: true # (optional) opt into the relaxed size tier; see .github/policy.yml (recipe_size_limits) for the exact numbers; omit if false (default)

architecture: # (optional) omit the whole block if nothing below can be inferred from code
agent: "single" # Options: [single | multi]
stateful: false # Options: [true | false]
datasources: # Options: [hardcoded | local | external]
- "external"

# dependencies: (optional) uncomment and fill in with canonical names
# libraries:
# - "ADK"
# - "pandas" # example — replace with actual libraries used
# services:
# - "GCP Project"
# - "Cloud Run" # example — replace with actual GCP/external services used

ownership:
team: "DEE"
poc: "happyhuman"
# contributors: (optional) uncomment and add GitHub IDs of additional contributors
# - "github-id-1"

# tags: (optional) uncomment and replace with meaningful labels
# - "rag"
# - "document-processing"

license: "Apache-2.0" # (optional) SPDX identifier; only set if explicitly declared — see https://spdx.org/licenses/
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ readme = "README.md"
requires-python = ">=3.11,<3.14"

dependencies = [
"google-adk>=1.28.0",
"google-adk>=1.31.0",
"google-cloud-aiplatform[adk,agent-engines]>=1.93.0",
"google-cloud-secret-manager>=2.16.0",
"opentelemetry-instrumentation-google-genai==0.4b0",
"python-dotenv>=1.0.1",
"reportlab==4.2.0",
Expand All @@ -29,7 +30,7 @@ dependencies = [

[dependency-groups]
dev = [
"google-adk[eval]>=1.28.0",
"google-adk[eval]>=1.31.0",
"pytest>=8.3.5",
"pytest-asyncio>=0.26.0",
"agent-starter-pack>=0.15.4",
Expand All @@ -43,27 +44,6 @@ dev = [
]


[tool.ruff]
extend = "../../../pyproject.toml"
target-version = "py311"


[tool.ruff.lint]
select = [
"E", # pycodestyle
"F", # pyflakes
"W", # pycodestyle warnings
"I", # isort
"C", # flake8-comprehensions
"B", # flake8-bugbear
"UP", # pyupgrade
"RUF", # ruff specific rules
]
ignore = ["E501", "C901"] # ignore line too long, too complex

[tool.ruff.lint.isort]
known-first-party = ["high_volume_document_analyzer"]

[tool.mypy]
disallow_untyped_calls = true
disallow_untyped_defs = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ async def test_agent_responds():
"""Agent should securely fetch mock documents and return a basic response."""
# This invokes Vertex AI using default GCP Application Credentials.
# Because USE_MOCK_API is True by default, it doesn't need Secret Manager or OAuth APIs.
import os

project = os.environ.get("GOOGLE_CLOUD_PROJECT", "")
if not project or project.startswith("<"):
pytest.skip(
"Integration test requires a configured GOOGLE_CLOUD_PROJECT."
)

response = await _run_agent(
"Please summarize the latest updates for collection 12345"
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runnability tests for the recipe."""

import os
from unittest.mock import MagicMock, patch


def test_agent_runnability() -> None:
"""Verify agent.py imports and defines the expected globals."""
# provide a dummy GCP project and patch google.auth.default() so import-time
# credential lookups don't need ADC — the setup must happen before the import.
os.environ.setdefault("GOOGLE_CLOUD_PROJECT", "test-project")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] env read carries a hardcoded default (second argument is a hardcoded default); 20 occurrence(s) across 6 file(s). Defaults belong in .env.example, not in the code (contrib/python/high-volume-document-analyzer/tests/test_runnability.py:24; and 5 more)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standardized placeholder strings in .env.example using <TODO: update-this-value> and removed hardcoded fallback defaults on runtime environment reads.


with patch(
"google.auth.default", return_value=(MagicMock(), "test-project")
):
import high_volume_document_analyzer.agent

assert high_volume_document_analyzer.agent.root_agent is not None
Loading
Loading