Skip to content

Feat/add ge demo generator#2963

Merged
holtskinner merged 40 commits into
GoogleCloudPlatform:mainfrom
ryotat7:feat/add-ge-demo-generator
Jul 13, 2026
Merged

Feat/add ge demo generator#2963
holtskinner merged 40 commits into
GoogleCloudPlatform:mainfrom
ryotat7:feat/add-ge-demo-generator

Conversation

@ryotat7

@ryotat7 ryotat7 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Follow the CONTRIBUTING Guide.
  • Ensure the tests and linter pass (Run ./scripts/format.sh from the repository root to format).
  • If you are creating a new notebook/sample:
  • You are listed as the author in your notebook or README file.
  • Your account is listed in CODEOWNERS for the file(s).

Summary of Changes:
This PR introduces the GE Demo Generator as a new Level 200–300 functional sample application under search/gemini-enterprise/ge-demo-generator/. It is a fully sanitized, public-compliant version of an internal GTM tool that allows users to instantly synthesize fully functional, domain-specific custom demo environments for Gemini Enterprise.

Key Components Included:

  • Apps Script frontend and backend for demo configuration and setup script generation (organized within a dedicated app/ subdirectory).
  • Automated deploy.sh for one-click provisioning of BigQuery datasets, Firestore collections, and Cloud Run agents.
  • Triple-Agent Autonomous Architecture using Gemini models and MCP tools.
  • Real-time Data Viewer application.
  • Comprehensive documentation condensed into a single README.md, alongside a standalone AGENTS.md file optimized for AI coding assistants.

All code files have been pre-pended with the required Apache 2.0 license headers, and the README.md includes the official Google non-product disclaimer.

@ryotat7
ryotat7 requested a review from a team as a code owner July 7, 2026 16:24
ryotat7 added 20 commits July 7, 2026 16:29
…nerator

# Conflicts:
#	.github/actions/spelling/allow.txt
…pdate URLs to official GoogleCloudPlatform repository paths
…eflect fully automated Cloud Run deployment and min-instances 0 configuration
Port the latest internal updates (v10.112 → v10.113) into the public version
via 3-way merge (base = internal v10.112). Only app/Code.gs and app/index.html
changed; the public version's deliberate removals are preserved.

- Add the interactive HTML dashboard tool (publish_dashboard) with signed-URL
  hosting; require fixed-height canvas + maintainAspectRatio:false to fix
  oversized dashboard charts; route interactive-dashboard requests to
  publish_dashboard instead of generate_image.
- Remove the dead LOGS_BUCKET_NAME artifact-service branch.

APP_VERSION -> v10.113-public. Validated: escaping validator passes; Code.gs
body and index.html scripts pass JS syntax checks; no internal-only leaks.
…ucket, and cleanup specification in README.md
…tual file statistics, Dataplex MCP connection, and GCS buckets
@ryotat7 ryotat7 assigned ryotat7 and unassigned ryotat7 Jul 8, 2026
… works

The Apache license header on app/Code.gs used shell-style `#` comments, which
are invalid in Google Apps Script (V8/JavaScript). `clasp push` (and deploy.sh,
which calls it) would fail / produce a broken deployment for anyone deploying
the files as-is. Convert the header to `//` line comments — valid Apps Script
and the correct comment style for a .gs (JavaScript) file — so the license text
is preserved and the project deploys without any manual header stripping.

(HTML files keep their `<!-- -->` headers: a comment before <!DOCTYPE html> is
valid and does not trigger quirks mode.)
@ryotat7
ryotat7 requested review from holtskinner and removed request for holtskinner July 8, 2026 15:06

@holtskinner holtskinner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This tool can be very useful, but right now, I think it's too complex/verbose to publish. Specifically Code.gs is incredibly large. Here are some recommendations for improvement/refactoring. Note: I'm not super well versed in Apps Script, so this is AI Generated, hopefully it all makes sense:

1. Summary (read this first)

Today app/Code.gs builds a bash script that writes Python files line-by-line
inside heredocs. Because code is hidden inside strings inside strings, every
\n, $, quote, and backtick must be escaped many times. This is the number
one cause of production bugs
and the reason AGENTS.md is 700 lines long.

The fix: keep the Python and JSON as real files in the git
repo
, and make the generated setup script download them at run time with
git or curl. The files never pass through Apps Script. All the escaping
problems disappear.


2. The problem today

Code.gs (JavaScript)
   ↓ builds, as one big escaped string
bash setup script  (setup-demo-xxx.sh)
   ↓ writes, via heredocs
Python files: agent.py, tools.py, fast_api_app.py, part_converters.py, viewer_app/main.py
   +  ~1,740 lines of A2UI example JSON

Problems this causes:

  • Escaping bugs. \n must become chr(10), f-strings become +, backticks
    become chr(96)*3, and so on. See AGENTS.md §2 and §9.
  • No testing. You cannot run ruff, mypy, or pytest on Python that only
    exists as a JavaScript string.
  • Hidden feature-flag bugs. Big ${ flag ? ... : '' } blocks span hundreds
    of lines. Code placed in the wrong block silently breaks (AGENTS.md §10, the
    Firestore incident).
  • A giant file. ~14,800 of the 17,738 lines of Code.gs are one template.

3. The recommendation (Option A)

3.1 Target architecture

Git repo (real, testable files)
   agent_template/
     adk_agent/app/agent.py
     adk_agent/app/tools.py
     adk_agent/app/fast_api_app.py
     adk_agent/app/part_converters.py
     adk_agent/app/__init__.py
     adk_agent/app/examples/0.8/*.json
     viewer_app/main.py
     Dockerfile
     requirements.txt   (etc.)
        │
        │  downloaded at run time by the setup script (git / curl)
        ▼
Cloud Shell → builds container → deploys to Cloud Run

Code.gs no longer emits any Python. It only emits a short bash script that:

  1. downloads agent_template/ from a pinned repo version,
  2. writes one small .env file with the per-demo values,
  3. runs the existing docker build / gcloud run deploy steps.

3.2 Move per-demo variation to environment variables

Right now, per-demo differences (which MCP servers, which model, the Data Viewer
URL, feature flags) are baked into the Python at generation time. With static
files, that variation must move somewhere else.

Make the Python fully static and read all variation from the environment.
This is the standard 12-factor pattern, and the project already does it in places
(e.g. os.environ.get("DATA_VIEWER_URL"), AGENTS.md §5).

# Before (baked in by Code.gs conditional block):
#   ${ enableWorkspaceMcp ? `toolsets.append(workspace_toolset())` : '' }

# After (static Python, decided at run time):
if os.environ.get("ENABLE_WORKSPACE_MCP") == "1":
    toolsets.append(workspace_toolset())

The setup script writes those flags into .env:

cat > .env <<__ENV_EOF__
PROJECT_ID=${projectId}
AGENT_MODEL=${agentModel}
AGENT_MODEL_LITE=${agentModelLite}
ENABLE_WORKSPACE_MCP=${enableWorkspaceMcp ? '1' : '0'}
BQ_DATASET=${datasetId}
__ENV_EOF__

.env is tiny, has no code in it, and needs almost no escaping. This also
removes the conditional-block hazard (AGENTS.md §10) entirely, because there
are no more code-emitting conditional blocks.


4. Why this is feasible in Apps Script (verified)

This project already uses every capability required:

Capability needed Already used in this repo
Run bash that fetches files Setup script already runs in Cloud Shell; Dockerfiles already git clone (AGENTS.md §3)
Keep real files out of the pushed GAS project .clasp.json uses "rootDir": "app"; deploy.sh, validate_examples.py, gebe-demo-generator/ already live in the repo but are not pushed
Emit a small orchestration script generateSetupScript already builds bash

Constraint respected: An Apps Script project can only contain .gs, .html,
and appsscript.json. Option A never puts .py/.json into the project — they
stay in the repo and are downloaded by the setup script. So the constraint does
not apply.


5. The fetch step (concrete)

Pin to a specific tag or commit so every demo is reproducible. Tie the ref to
the existing APP_VERSION in Code.gs.

Option A1 — sparse git clone (recommended: gets the whole tree in one call)

REPO_REF="ge-demo-v10.113"   # a git tag; keep in sync with APP_VERSION
SUBDIR="search/gemini-enterprise/ge-demo-generator/agent_template"

git clone --depth 1 --branch "$REPO_REF" --filter=blob:none --sparse \
  https://github.com/GoogleCloudPlatform/generative-ai.git _src
git -C _src sparse-checkout set "$SUBDIR"
cp -r "_src/$SUBDIR/." .
rm -rf _src

Option A2 — release tarball (no git needed)

REPO_REF="ge-demo-v10.113"
curl -sSfL \
  "https://github.com/GoogleCloudPlatform/generative-ai/archive/refs/tags/${REPO_REF}.tar.gz" \
  | tar -xz --strip-components=4 \
      "generative-ai-${REPO_REF}/search/gemini-enterprise/ge-demo-generator/agent_template"

Use whichever is simpler for your environment. Both leave the real agent_template
files in the working directory, ready for docker build.


6. What gets smaller

File Now After Option A
app/Code.gs 17,738 lines (Python + JSON embedded) roughly the ~3,000-line API/UI layer + a short fetch/deploy script
AGENTS.md 700 lines of escaping rules ~100 lines (only ADK {var} Rule #7 + the §6 anti-patterns survive)
validate_examples.py 103 lines (regex-extract + codecs.escape_decode) ~15 lines (json.load on the real examples/0.8/*.json)
Python files untestable strings real files — run ruff, mypy, pytest in CI

Escaping rules removed: #1, #2, #5, #6, #8, #9, #11, #12, #13, plus the
entire escaping changelog. Rule #7 stays — it is about ADK's runtime template
engine reading the LLM instruction, not about code generation.


7. Trade-offs and how to handle them

  • Network dependency at setup time. The setup script now downloads files.
    Cloud Shell always has internet, and the flow already fetches other things
    (supergateway, base images). Low risk. For air-gapped cases, keep the
    base64-embed option (not covered here) as a fallback.
  • Versioning / reproducibility. Always pin REPO_REF to a tag or commit SHA.
    Never fetch from main, or old demos will break when the repo changes. Cut a
    git tag whenever you bump APP_VERSION.
  • Two things to release together. The Apps Script push (clasp push) and the
    git tag for agent_template/ must match. Add a release checklist: bump
    APP_VERSION → tag the repo with the same version → clasp push.
  • The setup script is still generated bash. That part keeps a little
    escaping, but it is ordinary bash with a small .env — not Python-inside-
    heredoc-inside-JS. The painful layers are gone.

8. Suggested migration plan (incremental, low risk)

Do not move everything at once. Prove the pattern on the safest file first.

  1. Create agent_template/ in the repo and copy the current generated output
    of one file into it. Start with part_converters.py (most static, no feature
    flags).
  2. Switch the setup script to fetch that one file instead of writing it from a
    heredoc. Deploy a demo and confirm it still works.
  3. Move the A2UI example JSON (examples/0.8/*.json) next — this alone
    removes ~1,740 lines from Code.gs and simplifies validate_examples.py.
  4. Move tools.py, fast_api_app.py, agent.py, viewer_app/main.py,
    converting each ${ flag ? ... } block into a runtime os.environ check as
    you go.
  5. Trim AGENTS.md down to Rule #7 and the deployment anti-patterns.
  6. Set up the release checklist (tag == APP_VERSION).

Each step is independently shippable and independently reversible.


9. One-line takeaway

Keep code as code in the repo, download it at run time, and pass only small config
through .env — this deletes the most code, the most documentation, and the most
future bugs, with no new Apps Script capability required.

ryotat7 added 12 commits July 10, 2026 08:03
…e.gs (review step 1-3)

Per review feedback on GoogleCloudPlatform#2963: static Python/JSON that Code.gs embedded as
bash heredocs inside JS template literals now lives as real, testable files
under agent_template/. The generated setup script fetches them at run time
with a pinned sparse git checkout (git fetch --depth 1 --filter=blob:none
<TEMPLATE_REF> + cone sparse-checkout of the subdir; a full-repo tarball
would be ~159 MB) and copies them into place.

Extracted verbatim (behavior-preserving; verified byte-identical against the
previous generator output across 4 parameter matrices — 132 comparisons):
- adk_agent/app/fast_api_app.py (4,265 lines) and part_converters.py (316)
- adk_agent/app/__init__.py, .gitignore, pyproject.toml, .python-version,
  .dockerignore
- 24 A2UI example JSONs (examples/0.8/) — the five currency-bearing examples
  carry a [CURRENCY] placeholder the setup script substitutes, preserving the
  per-demo currency symbol behavior
- viewer_app/main.py (+ requirements.txt) — the three per-demo values
  (collection, dashboard title, description) are placeholder-substituted by
  the setup script after copy

Code.gs additions: TEMPLATE_REPO / TEMPLATE_REF / TEMPLATE_SUBDIR config
(Script-Properties overridable; REF pinned to a commit SHA — contributors
cannot tag this repo, and a SHA gives the same reproducibility), a
generation-time reachability check that surfaces a warning banner in the
script preview when the pinned ref is unreachable, and a clear runtime error
message in the fetch step. Code.gs shrinks 17,738 -> 10,112 lines.

validate_examples.py now validates the real files directly (json.loads on
every example, py_compile on every template Python file) instead of
regex-extracting from Code.gs; it no longer needs the a2ui package.

The template Python files carry file-level lint/type suppressions
(flake8/pylint/mypy/ruff): they are deployed as a runtime template into the
user's Cloud Shell rather than imported by repo tooling, and are validated
by py_compile plus end-to-end demo deployment. Alternatively the sample's
agent_template/ directory could be added to the super-linter
FILTER_REGEX_EXCLUDE (as done for finance-advisor-spanner and quickbot) —
left to the maintainers since it touches a shared workflow file.

agent.py / tools.py conversion to environment-driven configuration follows
in the next commit.
…a env + files (review step 4-6)

Completes the Option A migration from the GoogleCloudPlatform#2963 review: agent.py (2,563
lines) and tools.py (2,332 lines) are now static files in agent_template/.
All 28 generation-time interpolation points move to run-time configuration:

- Feature flags: `${ enableWorkspaceMcp ? ... }` / enableComputerUse blocks
  become `if os.environ.get("ENABLE_WORKSPACE_MCP") == "1":` guards (with
  no-op fallbacks when off). Flags are passed via .env and Cloud Run env.
- Scalars: dataset / collection / reference date / public dataset id are
  read from DEMO_DATASET, FS_COLLECTION, REFERENCE_DATE, PUBLIC_DATASET_ID.
- Generated system instruction: written by the setup script to
  adk_agent/app/generated_instruction.md; the static agent reads it at
  startup (same bytes as the previously embedded text).
- Imported MCP servers: the setup script writes adk_agent/app/mcp_config.json
  (name, safe name, entrypoint, required keys, port, auth type, precomputed
  in Code.gs); tools.py builds custom/Slack toolsets and agent.py builds the
  numbered instruction sections from it at startup.

Verified: the fully-resolved system instruction is byte-identical to the
previous generator output across all four parameter matrices (flags on/off ×
MCP combinations × public dataset × currency); both files pass py_compile;
the generated setup scripts pass bash -n; the emitted mcp_config.json matches
the equivalence-test fixture exactly.

Code.gs shrinks to 5,345 lines (from 17,738 before this series — a 70%
reduction; the remainder is the UI/API layer and bash orchestration).
APP_VERSION -> v10.114-public.

Docs: AGENTS.md rewritten (700 -> ~130 lines) — the multi-layer escaping
rules are obsolete; what remains is the architecture, template editing rules,
the ADK `{var}` instruction-engine hazard, TEMPLATE_REF pinning, a release
checklist, and the deployment anti-patterns. README updated (repo structure,
setup-script synthesis description).
Points the template fetch at the commit that contains the final
agent_template/ files, per the release checklist in AGENTS.md. During PR
review this resolves against the fork; switch to the upstream commit SHA
after merge (the TEMPLATE_REF Script Property can override at any time).
…regex; rename py_files

The spell-check pipeline flagged `jsons` in the extracted fast_api_app.py —
which exposed a latent escaping bug the extraction inherited faithfully: the
A2UI tag-debris regex was written as `\s` directly inside the Code.gs JS
template literal, where JavaScript resolves the unrecognized escape `\s` to
plain `s`. Deployed agents were therefore matching literal `s` characters
(`<s*/?s*a2ui[-_]jsons*>`) instead of whitespace. Restore the intended
`<\s*/?\s*a2ui[-_]json\s*>?|a2ui[-_]json\s*>` — trivial to express correctly
now that the file is real Python. (Nearby patterns that used the documented
`chr(92)` workaround were unaffected; this was the exact bug class the
template extraction is meant to eliminate.)

Also rename `pyfiles` to `py_files` in validate_examples.py for the spelling
checker.
… the template

The repo root .gitignore lists `.python-version`, so `git add` silently
skipped it and the pushed agent_template/ was missing the file — the setup
script then failed at `cp .../agent_template/.python-version` (caught by the
first end-to-end run). Store the template copy under a non-ignored name
(python-version.txt); the setup script copies it into place as
`.python-version` in the demo project, so the deployed layout is unchanged
and future contributors cannot retrigger the silent skip.
…browser agent

End-to-end testing surfaced a 400 at step 0 of every browser session:
"computer use is not supported for this model in this region". The loop was
reusing AGENT_MODEL (gemini-3.5-flash) for the computer_use tool, which only
works in projects allowlisted for the tool on general models. Switch the
loop to the dedicated computer-use model (COMPUTER_USE_MODEL env override,
default gemini-2.5-computer-use-preview-10-2025), verified against Vertex in
a non-allowlisted project: the dedicated model on the global endpoint
returns the expected computer-use function calls, while gemini-3.5-flash is
rejected. Allowlisted projects can still opt back via COMPUTER_USE_MODEL.
…_SUBDIR Script Properties

The template-source pinning properties were only mentioned in the System
Architecture table; list them in the Script Properties section where all
other configuration is documented, including when TEMPLATE_REF must be
updated and what overriding the defaults is for.
@ryotat7

ryotat7 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the detailed review — the Option A direction was right, and it is now fully implemented, following the incremental order from your §8.

What changed (commits 53e6a51c230d6f):

  • All embedded Python/JSON now lives as real files under agent_template/ (agent.py, tools.py, fast_api_app.py, part_converters.py, 24 A2UI example JSONs, the data-viewer app, and the small static build files).
  • The generated setup script fetches the template at run time and writes only small per-demo config next to it: .env (scalars + ENABLE_WORKSPACE_MCP / ENABLE_COMPUTER_USE flags), generated_instruction.md, and mcp_config.json. All 28 generation-time interpolation points became run-time configuration — the ${ flag ? ... } conditional-block hazard is gone.
  • app/Code.gs: 17,738 → 5,345 lines (the remainder is the UI/API layer and bash orchestration). AGENTS.md: 700 → ~130 lines (the escaping rulebook is obsolete; what survives is the architecture, the ADK {var} hazard, pinning, and a release checklist). validate_examples.py now validates the real files directly (json.loads + py_compile, no a2ui dependency).

How it was verified

  • A Node harness runs generateSetupScript outside Apps Script across four parameter matrices (flags on/off × imported MCPs × public dataset × currency). The extracted files are byte-identical to the previous generator output (132 comparisons), and the fully-resolved system instruction is byte-identical in all four matrices.
  • bash -n on every generated script; py_compile on every template file; end-to-end: a demo generated by the refactored app deployed to Cloud Run from the fetched template and ran in Gemini Enterprise.

Deviations from the proposal, with reasons

  • Pinned by commit SHA, not a tag (TEMPLATE_REPO / TEMPLATE_REF / TEMPLATE_SUBDIR in CONFIG, Script-Properties overridable): contributors cannot create tags in this repository. If you prefer the tag flow, a maintainer-created tag on the merge commit would work as the default ref and would also avoid a one-line follow-up PR to re-point the default at the upstream commit after merge — happy to go either way.
  • Sparse git checkout instead of a tarball: a full-repo tarball at a SHA is ~159 MB for this monorepo; the blob-filtered sparse fetch is ~1 MB and ~1.6 s.
  • Lint: the template Python carries file-level suppressions (it is a runtime template deployed into the user's Cloud Shell, validated by py_compile and end-to-end deploys; the repo's strict mypy/pylint would require typing ~9k lines of runtime code up front). Adding agent_template/ to FILTER_REGEX_EXCLUDE — the same treatment as finance-advisor-spanner/ and quickbot/ — would be cleaner, but that is a workflow-file change, so I left it to you. CI is green as-is.
  • Per your §7, the genuinely per-demo bash stays generated (Dockerfile assembly with per-MCP clones, requirements extras, BigQuery CSV data, Firestore seed script).

A bonus find: the extraction surfaced a real production bug. The A2UI tag-debris regex was written with \s directly inside the JS template literal; JavaScript resolves the unrecognized escape to plain s, so deployed agents were matching literal s characters instead of whitespace. It is now written correctly in the real Python file (92dbb3d) — exactly the bug class this migration eliminates.

Ready for another look whenever you have time.

@ryotat7
ryotat7 requested a review from holtskinner July 10, 2026 13:56
Comment thread search/gemini-enterprise/ge-demo-generator/agent_template/viewer_app/main.py Outdated
ryotat7 added 2 commits July 10, 2026 14:59
…cument the A2UI examples

Review follow-ups:

- viewer_app: the two embedded HTML pages move out of main.py into
  viewer_app/templates/ (viewer.html — the dashboard, 1,022 lines;
  browser_view.html — the Computer Use live view). main.py shrinks from
  1,393 to 293 lines of plain Python and loads the files at startup. Serving
  is unchanged: the dashboard is still returned as-is (not Jinja-rendered —
  the page's inline JavaScript contains brace sequences) and the live view
  still goes through render_template_string with its session_id variable.
  The setup script now copies templates/ and substitutes the dashboard
  title/description placeholders into viewer.html (collection id stays in
  main.py). Verified: the final served pages are byte-identical to the
  previous embedded strings (modulo the license comment header).

- examples/0.8/README.md: documents that these JSONs are app-specific
  few-shot examples supplied to the a2ui-agent-sdk through its documented
  BasicCatalog.get_config(examples_path=...) extension point and validated
  by the library at startup — the A2UI schema itself comes from the library.
@ryotat7
ryotat7 requested a review from holtskinner July 10, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants