docs(integrations): add Google ADK integration guide - #1508
Conversation
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
| @@ -0,0 +1,3 @@ | |||
| google-adk>=2.0.0 | |||
| e2b-code-interpreter | |||
There was a problem hiding this comment.
This repo's own tests/e2e/sdk_compat/e2b-versions.txt documents that e2b-code-interpreter 2.9.x declares e2b>=2.26.0,<3.0.0 but crashes with e2b>=2.38.0 (TypeError: get_transport() got an unexpected keyword argument 'http2'). With e2b-code-interpreter unpinned, a fresh pip install -r requirements.txt can resolve exactly that broken pair, and the example fails at the very first Sandbox.create(...).
The offline smoke test cannot catch this — it only imports cube_code_tool and never constructs a Sandbox, so it passes even on a broken install (consistent with the "Not verified" note in the PR description). Consider pinning the validated pair (e.g. e2b-code-interpreter==2.9.1 with a compatible e2b), matching the docs' own advice to "pin the resolved versions after validating".
| def _execution_to_dict(execution: Any, stdout: list[str]) -> dict[str, Any]: | ||
| """Return a JSON-serializable result across E2B SDK versions.""" | ||
| result: dict[str, Any] = { | ||
| "stdout": "".join(stdout), |
There was a problem hiding this comment.
"".join(stdout) assumes the on_stdout callback delivers str. The repo's own examples/openai-agents-code-interpreter/code_interpreter_demo_ci.py treats run_code's on_stdout callback as receiving OutputMessage objects (it reads msg.line), and tests/e2e/sdk_compat/adapters/e2b_adapter.py str()-coerces each execution.logs.stdout item before joining — both indicating the callback/attr type is not a plain str in every SDK version. If the installed SDK delivers anything but str, "".join(stdout) raises TypeError.
Since the smoke test never calls run_code, this exact path is unverified. Reading execution.logs.stdout (coercing each item with str()) as the sdk_compat adapter does would be more robust than relying on the callback type, or at minimum coerce here: "stdout": "".join(str(line) for line in stdout).
| ``` | ||
|
|
||
| Compared with a host-side Python tool, only the tool body changes. The ADK agent | ||
| still sees a regular function tool with structured inputs and outputs. |
There was a problem hiding this comment.
execution.text is the last-expression text representation, not the program's stdout — the runnable example in cube_code_tool.py keeps stdout and text as separate fields for exactly this reason. For a snippet that only print(...)s (as in the Fibonacci example below) with no trailing expression, execution.text is empty, so a reader following this doc snippet would see an empty stdout despite the program printing. Suggest mirroring the example's separation ("stdout": <captured stdout>, "text": execution.text). Also, the snippet uses os.environ but the surrounding code block has no import os.
Review: docs(integrations): add Google ADK integration guide (#1508)AI-generated review — findings below are the reviewer's own and have not been independently validated by a human. SummaryThis PR adds a bilingual Google ADK integration guide ( Overall this is a solid, well-scoped docs + example PR. It closely follows the established conventions of the existing integration guides (e.g. Main risk (disclosed by the author)The PR is upfront that Findings
Minor nits (not blockers)
Verification notes
|
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
| - Python 3.10+ on the machine running the ADK agent. | ||
| - A Google API key or another ADK-supported model configuration. | ||
|
|
||
| ::: warning Control plane and data plane |
There was a problem hiding this comment.
This warning block is titled "Control plane and data plane" but only covers the self-signed-TLS / CUBE_SSL_CERT_FILE case. It omits the data-plane reachability requirement that will actually block the Runnable Demo: run_code executes over the sandbox data plane, and the official E2B SDK (which this example uses via e2b-code-interpreter) hardcodes its DNS resolution flow, so <port>-<sandbox_id>.cube.app must resolve to CubeProxy (wildcard DNS) or the E2B dev-sidecar must be used. Without one of these, Sandbox.create (control plane) succeeds but run_code (data plane) fails. The repo already documents this in docs/guide/connect-existing-cluster.md (Options C/D) and the OpenAI Agents SDK guide's equivalent warning; the same gap exists in the Chinese version of this file.
| - 运行 ADK agent 的机器上有 Python 3.10+。 | ||
| - Google API key,或其他 ADK 支持的模型配置。 | ||
|
|
||
| ::: warning 控制面与数据面 |
There was a problem hiding this comment.
这个警告块的标题是"控制面与数据面",但正文只覆盖了自签 TLS / CUBE_SSL_CERT_FILE 的情况,漏掉了真正会挡住示例运行的数据面可达性要求:run_code 走的是沙箱数据面,而官方 E2B SDK(本例通过 e2b-code-interpreter 使用)内部硬编码了 DNS 解析流程,需要让 <port>-<sandbox_id>.cube.app 解析到 CubeProxy(wildcard DNS)或使用 E2B dev-sidecar。否则 Sandbox.create(控制面)成功、run_code(数据面)却会失败。仓库的 docs/guide/connect-existing-cluster.md(Option C/D)与 OpenAI Agents SDK 集成指南的同名警告已写明这一点,建议在正文中补充。
|
|
||
| def main() -> None: | ||
| assert agent.root_agent.name == "cube_code_agent" | ||
| assert cube_code_tool.run_python_in_cube in agent.root_agent.tools |
There was a problem hiding this comment.
This assertion relies on Agent.tools preserving the raw function object identity. google-adk normalizes callables passed via tools= in some versions (wrapping them into FunctionTool), which would make run_python_in_cube in agent.root_agent.tools false even though the wiring is correct. It passes against the validated 2.7.1, but it couples the offline check to a non-public ADK implementation detail that is likely to shift as ADK evolves (the guide itself notes ADK changes quickly). Consider asserting on a resolved tool's name/underlying function instead, e.g. any(getattr(t, "name", None) == "run_python_in_cube" for t in agent.root_agent.tools).
| - **Reuse a sandbox per session:** the example creates one temporary sandbox per | ||
| tool call for simplicity. For multi-step notebooks, keep a sandbox handle in a | ||
| session-scoped service and delete it when the ADK session ends. | ||
| - **Timeouts:** set `CUBE_SANDBOX_TIMEOUT` or pass a fixed timeout to |
There was a problem hiding this comment.
CUBE_SANDBOX_TIMEOUT is read by cube_code_tool.py (default 300) and mentioned here, but it is missing from the Setup env-var table above and from .env.example, so a reader following the guide can't discover it from the configuration docs. Also worth clarifying: this value is passed to Sandbox.create(timeout=...) (the sandbox auto-shutdown bound), not to run_code — the example never passes a per-call execution timeout to run_code. The same applies to the Chinese version.
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
|
|
||
| stdout: list[Any] = [] | ||
| with Sandbox.create(template=template_id, timeout=timeout) as sandbox: | ||
| execution = sandbox.run_code(code, on_stdout=stdout.append) |
There was a problem hiding this comment.
run_code is invoked without a per-call execution timeout, so generated code that loops or runs long blocks the ADK tool call until the sandbox's auto-reclaim timeout (the CUBE_SANDBOX_TIMEOUT passed to Sandbox.create) kills the sandbox — the docs correctly note this is not a per-call timeout. A runaway snippet (e.g. while True: pass) will hang the whole ADK turn for the sandbox lifetime, then fail in a way the model can't easily interpret. Consider passing a per-call timeout to run_code (e.g. a configurable timeout= on the tool), so long/infinite snippets fail fast instead of blocking the agent.
| """Return a JSON-serializable result across E2B SDK versions.""" | ||
| logs = getattr(execution, "logs", None) | ||
| captured_stdout = getattr(logs, "stdout", None) if logs else None | ||
| stdout_items = captured_stdout if captured_stdout is not None else stdout |
There was a problem hiding this comment.
The cross-version fallback is defeated by the is not None check: if a given SDK version exposes execution.logs.stdout as an empty list while the real output only arrived through the on_stdout callback (stdout), this picks the empty list and the returned stdout is silently empty. The docstring says the goal is a "JSON-serializable result across E2B SDK versions" — prefer the non-empty source (if captured_stdout), so whichever source actually captured output wins.
|
|
||
| cube_ssl = os.environ.get("CUBE_SSL_CERT_FILE") | ||
| if cube_ssl and Path(cube_ssl).is_file(): | ||
| os.environ.setdefault("SSL_CERT_FILE", cube_ssl) |
There was a problem hiding this comment.
SSL_CERT_FILE is process-global: exporting the Cube CA bundle here affects every HTTPS connection in the ADK process, including the model provider (e.g. generativelanguage.googleapis.com). If the bundle only contains the self-signed Cube CA, Google API calls will fail TLS verification. The LangChain guide documents this exact caveat (CUBE_SSL_CERT_FILE "…the bundle should include public root CAs or the LLM endpoint is affected too") — worth stating here as well, since the ADK guide recommends this variable for self-signed deployments.
| - Data-plane reachability for the official E2B SDK. A one-click local | ||
| deployment includes CoreDNS; production deployments should configure wildcard | ||
| DNS, and local setups without wildcard DNS can use the | ||
| [E2B development sidecar](/guide/connect-existing-cluster). |
There was a problem hiding this comment.
The "use the E2B development sidecar" fallback isn't wired into the shipped example. The sidecar only works with the official E2B SDK when setup_dev_sidecar() from examples/e2b-dev-sidecar/dev_sidecar.py is called in the same process — it monkey-patches ConnectionConfig.get_sandbox_url / SandboxBase.get_host to route data-plane traffic through the proxy. cube_code_tool.py never calls it, so a local deployment without wildcard DNS will fail at run_code with data-plane DNS errors even after following this pointer. Consider either wiring the sidecar in (opt-in via env var, matching the existing sidecar example) or explicitly noting that users must call setup_dev_sidecar() before creating sandboxes.
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
| @@ -0,0 +1,22 @@ | |||
| # CubeSandbox E2B-compatible control plane. | |||
| E2B_API_URL="http://127.0.0.1:3000" | |||
There was a problem hiding this comment.
Nit: this default (http://127.0.0.1:3000) conflicts with the default in examples/e2b-dev-sidecar/env.example (http://127.0.0.1:13000), which the guide points users to for the local-development-without-wildcard-DNS flow. In a one-click dev-env deployment CubeAPI's host port is 13000 (guest 3000), so a user following the documented sidecar path with this default .env will target the wrong control-plane port and get a connection error. Consider aligning the two defaults or adding a comment about the dev-env host port mapping (as the sidecar env.example does).
| [`examples/e2b-dev-sidecar`](https://github.com/TencentCloud/CubeSandbox/tree/master/examples/e2b-dev-sidecar) | ||
| before sandbox creation, so configure that example's `CUBE_REMOTE_PROXY_*` | ||
| variables as well. | ||
|
|
There was a problem hiding this comment.
Suggestion: "configure that example's CUBE_REMOTE_PROXY_* variables" is easy to misread as "edit examples/e2b-dev-sidecar/.env". But setup_dev_sidecar() reads those variables straight from the process environment (it does not call load_dotenv), and this example's load_environment() only loads examples/google-adk-integration/.env (or cwd/.env). A user who follows the sidecar example's own README (cp env.example .env in that directory) will find the variables silently ignored. Consider rewording to something like "set the CUBE_REMOTE_PROXY_* variables documented in examples/e2b-dev-sidecar in this example's .env (or export them in the environment)". The same wording appears in the Chinese guide and the example READMEs.
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
| stdout_items = captured_stdout or stdout | ||
|
|
||
| result: dict[str, Any] = { | ||
| "stdout": "".join(_stringify_log_item(item) for item in stdout_items), |
There was a problem hiding this comment.
stderr is never surfaced. _execution_to_dict only joins execution.logs.stdout (and the on_stdout callback); execution.logs.stderr / on_stderr are never collected, and the return dict has no stderr field. For a coding agent this matters more than stdout: Python tracebacks and sys.stderr writes are exactly what the model needs to diagnose a failed sandbox run, and str(execution.error) alone usually omits the traceback. Note the migration example in the docs replaces a local tool that returned stderr with one that silently drops it. Consider adding stderr to the result, e.g. "stderr": "".join(_stringify_log_item(item) for item in stderr_items) from execution.logs.stderr (falling back to an on_stderr callback, mirroring the stdout handling).
| | CubeSandbox | E2B-compatible CubeAPI, a reachable CubeProxy data plane, and a template that supports `run_code` | | ||
| | SDK path | `e2b==2.26.0` and `e2b-code-interpreter==2.8.1`, pointed at CubeAPI through `E2B_API_URL` | | ||
|
|
||
| Google ADK is evolving quickly. The example pins the E2B packages to a pair |
There was a problem hiding this comment.
The claim "recorded in the repository's SDK compatibility notes" is accurate, but the nuance matters here: tests/e2e/sdk_compat/e2b-versions.txt explicitly says the e2b==2.26.0 row was validated with the interpreter-dependent cases deselected and "says nothing about run_code on those cores." The only run_code-validated pairing recorded for e2b-code-interpreter==2.8.1 is e2b==2.21.0 (via a constraint override). Since this example's entire surface is run_code — and the PR itself notes no live run was performed — I'd either validate e2b==2.26.0 + e2b-code-interpreter==2.8.1 against a cluster before merging, or add one line here stating that the pair is plain-pip-resolvable but its run_code path is not part of the recorded validation surface.
| @@ -0,0 +1,5 @@ | |||
| google-adk>=2.0.0 | |||
There was a problem hiding this comment.
google-adk>=2.0.0 is left floating while the E2B packages are pinned. The docs (and this file's own README) justify pinning E2B precisely because "Google ADK is evolving quickly" — the same reasoning argues for pinning ADK to the version you actually validated (2.7.1), otherwise a future pip install can silently pull a breaking ADK release into an example that is otherwise treated as a reproducible, recorded pairing.
| template_id = os.environ["CUBE_TEMPLATE_ID"] | ||
| with Sandbox.create(template=template_id, timeout=300) as sandbox: | ||
| execution = sandbox.run_code(code, timeout=60) | ||
| stdout = "".join(str(item) for item in execution.logs.stdout) |
There was a problem hiding this comment.
This snippet diverges from the checked-in cube_code_tool.py in a way a copy-pasting reader will trip on: it hardcodes timeout=300/timeout=60 (the env-var-driven defaults documented in the Setup table are ignored), it returns {"stdout", "text", "error"} without the results_count field that the "Migrating a local ADK code tool" section references as part of this example's shape, and str(item) on execution.logs.stdout items only produces the intended text when the SDK yields plain strings — on versions where the items are LogItem objects you'd want .line (which is why the runnable example uses _stringify_log_item). Consider aligning the snippet with the example (or explicitly labeling it "simplified, see the runnable example").
Signed-off-by: ztt0216 <tianzhong@student.unimelb.edu.au>
| assert fallback["stdout"] == "callback\n" | ||
| assert fallback["stderr"] == "callback error\n" | ||
|
|
||
| empty_code = cube_code_tool.run_python_in_cube(" ") |
There was a problem hiding this comment.
The "offline smoke test" never exercises the real SDK call surface. The only code path it actually runs is the empty-code early-return branch; the rest tests _execution_to_dict against hand-built fakes. So a signature change in the pinned pair — e.g. Sandbox.create() rejecting timeout, or Sandbox.run_code() rejecting on_stdout/on_stderr/timeout — would pass the smoke test and only fail at live runtime. Since this example explicitly does not validate the live path, consider adding cheap signature-inspection assertions (the repo already has this pattern: _accepts_keyword in tests/e2e/sdk_compat/adapters/e2b_adapter.py) so the wiring check at least catches version drift against the installed SDK.
|
|
||
| error = getattr(execution, "error", None) | ||
| if error: | ||
| result["error"] = str(error) |
There was a problem hiding this comment.
Docs (and the tool docstring) promise that the returned stderr/error let the agent "inspect tracebacks and diagnostic output from failed runs." In e2b-code-interpreter an uncaught exception in the cell is delivered on execution.error (an ExecutionError with .name/.value/.traceback), not necessarily on logs.stderr, and str(error) here drops error.traceback. Worth verifying against the pinned pair on a live run; if the traceback doesn't survive, surface it explicitly, e.g. getattr(error, "traceback", None).
|
|
||
|
|
||
| def _stringify_log_item(item: Any) -> str: | ||
| line = getattr(item, "line", None) |
There was a problem hiding this comment.
_stringify_log_item handles strings and objects with a .line attribute, but a dict chunk (e.g. {"line": "hello", "type": "stdout"}, which some E2B SDK versions hand to the callbacks) falls through getattr(item, "line", None) to str(item), rendering the Python repr instead of the output line. Since the stated goal is cross-version output normalization, handle mapping items too (e.g. item.get("line") if isinstance(item, dict) else ...).
Summary
docs/guide/integrations/anddocs/zh/guide/integrations/.e2b-code-interpreterSDK.examples/google-adk-integration/example with an ADKroot_agent, Cube-backed code execution tool, bilingual README files, and an offline smoke test.Related to #244.
Validation
python3 -m py_compile examples/google-adk-integration/*.pygoogle-adk 2.7.1e2b-code-interpreter 2.9.1/tmp/cubesandbox-google-adk-venv/bin/python examples/google-adk-integration/smoke_test.pyGOOGLE_ADK_CUBE_SMOKE_OKcd docs && npm install && npm run docs:buildgit diff --checkNot verified
run_code-capableCUBE_TEMPLATE_IDwere available in this environment.