ADK Agent Container Image - #6894
Conversation
| @@ -0,0 +1,56 @@ | |||
| # ADK Agent Container | |||
|
|
|||
| FastAPI service deployed inside the GKE cluster that serves as the | |||
| sandbox-side metrics. | ||
| """ | ||
| async with _benchmark_lock: | ||
| os.environ["BENCHMARK_MODE"] = "density" |
There was a problem hiding this comment.
Not urgent, but maybe for a followup PR:
Using os env to pass configs is not very intuitive, and also has the risk of race conditions. Consider using the prompt to pass the configs
POST /benchmark/python/density
|
v
FastAPI service
|
| (prompt: {mode: "density", sample_count: 100...})
v
ADK agent
|
| (prompt: {mode: "density", sample_count: 100...})
v
MockLLM
|
| (script instruction based on the prompt)
v
ADK agent (execution)
There was a problem hiding this comment.
@roycaihw Using os.environ is a design choice to keep the Python benchmark scripts static. If we passed parameters via the prompt, our MockLlm would have to perform string-replacement on the Python script's source code to inject the variables (e.g., changing SAMPLE_COUNT = 20 to 100) before sending it to the sandbox. With os.environ, the Python script remains static, and we cleanly inject the variables into the sandbox via standard Linux environment variables (/bin/sh -c 'SAMPLE_COUNT=100 python3 script.py').
Regarding race conditions: we explicitly use an asyncio.Lock() (_benchmark_lock) around the endpoint handlers to serialize benchmark requests, ensuring concurrent POSTs cannot clobber the shared environment variables.
| req.concurrent_sessions, | ||
| ) | ||
|
|
||
| prompt = "Please start the GKE performance benchmark workflow." |
There was a problem hiding this comment.
The prompt is not used, correct? Can we drop it?
There was a problem hiding this comment.
You are correct that the content of the prompt is ignored by our MockLlm. However, the ADK Runner.run_async() method requires a non-empty string to initiate the conversation loop and trigger the first LLM generation. I can simplify the string to just "start" to make it clear that it is simply a trigger, but we cannot drop the variable entirely.
| ) | ||
|
|
||
|
|
||
| class ChromiumBenchmarkRequest(BaseModel): |
There was a problem hiding this comment.
Right now the main.py combines all the endpoints and their Request class. For better readability, can you split each endpoint and its Request class into a separate file?
There was a problem hiding this comment.
I'll refactor into a "modular" FastAPI structure with an api/routes/ directory, moving each endpoint and its models into separate files.
| _benchmark_lock = asyncio.Lock() | ||
|
|
||
|
|
||
| def _percentile_stats(sorted_values: list, prefix: str) -> dict: |
There was a problem hiding this comment.
Can you turn this function into a util for reuse? You have two methods (_percentile_stats and _percentile). Make them consistent.
There was a problem hiding this comment.
I'll consolidate the percentile logic into a shared api/utils.py file, sure.
| logging.exception("Error shutting down ThreadPoolExecutor") | ||
|
|
||
|
|
||
| app = FastAPI(title="GKE Benchmark Agent", version="0.2.0", lifespan=lifespan) |
| POST /benchmark/chromium/density → run the Chromium density benchmark | ||
| POST /run → raw ADK agent interaction | ||
|
|
||
| POST /benchmark/python/density — Request: |
There was a problem hiding this comment.
The documentation seems incomplete as it only has the request and response for POST /benchmark/python/density. I'd recommend putting the API endpoint documentation in a separate doc file.
There was a problem hiding this comment.
I'll move the API doc out of main.py and into a new API_DOCS.md file.
| elapsed_ms = (time.perf_counter() - t0) * 1000 | ||
|
|
||
| print(json.dumps({ | ||
| "sandbox_status": "ok", |
There was a problem hiding this comment.
Let's make this a "success" v.s. "fail" based on whether result matches the expectation (49,995,000-- the sum of all integers from 0 up to 9,999)
| print(json.dumps({ | ||
| "sandbox_status": "ok", | ||
| "sandbox_qps_exec_ms": round(elapsed_ms, 3), | ||
| "sandbox_compute_result": result, |
There was a problem hiding this comment.
Let's drop the "sandbox_compute_result" since it's just confusing to people unless they read the actual source of what the sandbox does.
|
|
||
| print(json.dumps({ | ||
| "sandbox_status": "ok", | ||
| "sandbox_qps_exec_ms": round(elapsed_ms, 3), |
There was a problem hiding this comment.
Let's drop "qps" from the metrics name since this metrics measures how long a simple python sandbox execution takes-- nothing related to qps here.
|
Thanks for the review Roy. Give me a ping when Roy's lgtm'ed & I'll add the lgtm + tag which will start the merging process |
20a9108 to
0e7ea72
Compare
| router = APIRouter() | ||
|
|
||
| class RunRequest(BaseModel): | ||
| prompt: str = "start" |
There was a problem hiding this comment.
nit: "start" seems unused. Consider removing it.
| output = await run_agent(req.prompt) | ||
| return {"response": output} | ||
| except Exception as e: | ||
| logger.exception("Agent run failed") |
| "sample_count": 100, | ||
| "sample_warmup": 5, | ||
| "concurrent_sessions": 1, | ||
| "sandbox_exec_timeout_s": 60 |
There was a problem hiding this comment.
The file is titled API_DOCS but it seems to only contain some examples with no description? Please document the request and response API schema.
| ### `POST /benchmark/python/qps` | ||
| Runs the QPS saturation benchmark. | ||
| **Request:** | ||
|
|
| app = App( | ||
| name=root_agent.name, | ||
| root_agent=root_agent, | ||
| # enable_tracing=True, |
| logging.info("SANDBOX_TIMINGS_DELETE: delete_ms=%.3f", delete_ms) | ||
|
|
||
|
|
||
| gke_executor = BenchmarkGkeCodeExecutor( |
|
|
||
| gke_executor = BenchmarkGkeCodeExecutor( | ||
| cluster_name=os.getenv("CLUSTER_NAME"), | ||
| location=os.getenv("GOOGLE_CLOUD_LOCATION"), |
| _SANDBOX_POOL = ThreadPoolExecutor(max_workers=_sandbox_io_workers()) | ||
|
|
||
|
|
||
| class BenchmarkGkeCodeExecutor(GkeCodeExecutor): |
Files: ~13 new files (no Python benchmark logic)
perfkitbenchmarker/data/docker/agentic/adk-agent/README.mdperfkitbenchmarker/data/docker/agentic/adk-agent/Dockerfileperfkitbenchmarker/data/docker/agentic/adk-agent/__init__.pyperfkitbenchmarker/data/docker/agentic/adk-agent/.dockerignoreperfkitbenchmarker/data/docker/agentic/adk-agent/.gcloudignoreperfkitbenchmarker/data/docker/agentic/adk-agent/cloudbuild-arm64.yamlperfkitbenchmarker/data/docker/agentic/adk-agent/requirements.txtperfkitbenchmarker/data/docker/agentic/adk-agent/main.pyperfkitbenchmarker/data/docker/agentic/adk-agent/gke_performance_agent/__init__.pyperfkitbenchmarker/data/docker/agentic/adk-agent/gke_performance_agent/agent.pyperfkitbenchmarker/data/docker/agentic/adk-agent/sandboxed_apps/python_test_app/benchmark_density.pyperfkitbenchmarker/data/docker/agentic/adk-agent/sandboxed_apps/python_test_app/benchmark_payload.pyperfkitbenchmarker/data/docker/agentic/adk-agent/sandboxed_apps/python_test_app/benchmark_qps.pyDescription: Adds the ADK Agent container image — a FastAPI service deployed inside the GKE cluster that the PKB benchmark modules interact with via HTTP. Includes:
BenchmarkGkeCodeExecutorNothing in PKB imports these files — they are copied into a container image at build time.