Feat/add ge demo generator#2963
Conversation
…nerator # Conflicts: # .github/actions/spelling/allow.txt
…project to search/gemini-enterprise
… and deploy script
…pdate URLs to official GoogleCloudPlatform repository paths
…oper tools compatibility
…eflect fully automated Cloud Run deployment and min-instances 0 configuration
… pass spell-check pipeline
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
… 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.)
There was a problem hiding this comment.
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.
\nmust becomechr(10), f-strings become+, backticks
becomechr(96)*3, and so on. SeeAGENTS.md§2 and §9. - No testing. You cannot run
ruff,mypy, orpyteston 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.gsare 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:
- downloads
agent_template/from a pinned repo version, - writes one small
.envfile with the per-demo values, - runs the existing
docker build/gcloud run deploysteps.
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 _srcOption 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_REFto a tag or commit SHA.
Never fetch frommain, or old demos will break when the repo changes. Cut a
git tag whenever you bumpAPP_VERSION. - Two things to release together. The Apps Script push (
clasp push) and the
git tag foragent_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.
- Create
agent_template/in the repo and copy the current generated output
of one file into it. Start withpart_converters.py(most static, no feature
flags). - Switch the setup script to fetch that one file instead of writing it from a
heredoc. Deploy a demo and confirm it still works. - Move the A2UI example JSON (
examples/0.8/*.json) next — this alone
removes ~1,740 lines fromCode.gsand simplifiesvalidate_examples.py. - Move
tools.py,fast_api_app.py,agent.py,viewer_app/main.py,
converting each${ flag ? ... }block into a runtimeos.environcheck as
you go. - Trim
AGENTS.mddown to Rule #7 and the deployment anti-patterns. - 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.
…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.
…for the browser agent" This reverts commit 2b829c9.
…_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.
|
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
How it was verified
Deviations from the proposal, with reasons
A bonus find: the extraction surfaced a real production bug. The A2UI tag-debris regex was written with Ready for another look whenever you have time. |
…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.
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:
CONTRIBUTINGGuide../scripts/format.shfrom the repository root to format).READMEfile.CODEOWNERSfor 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:
app/subdirectory).deploy.shfor one-click provisioning of BigQuery datasets, Firestore collections, and Cloud Run agents.README.md, alongside a standaloneAGENTS.mdfile optimized for AI coding assistants.All code files have been pre-pended with the required Apache 2.0 license headers, and the
README.mdincludes the official Google non-product disclaimer.