GCP Cloud Functions¶
Google Cloud Functions (2nd gen) is the simplest deployment option for ModernPath agents. The framework provides the gcpHttp() adapter that wraps your handler into the Express-compatible (req, res) => void signature expected by the GCP Functions Framework.
How It Works¶
The gcpHttp() adapter performs three steps:
- Converts the Express
reqobject into a platform-neutralHttpEvent. - Passes the event to your framework handler (
createAgentExecuteHandlerorcreateAgentExecuteStreamHandler). - Writes the
JsonResponseback to the Expressresobject.
sequenceDiagram
participant CF as Cloud Function
participant Adapter as gcpHttp()
participant Handler as createAgentExecuteHandler
participant Agent as Your Agent
CF->>Adapter: req, res
Adapter->>Handler: HttpEvent
Handler->>Agent: AgentContext
Agent-->>Handler: AgentResult
Handler-->>Adapter: JsonResponse
Adapter-->>CF: res.status().send() Entry Point¶
Basic (JSON response)¶
import {
createAgentExecuteHandler,
gcpHttp,
GeminiClient,
PromptTemplate,
ToolRegistry,
} from "@modernpath/agent-framework";
import { MyAgent } from "./agents/MyAgent";
// Build dependencies once (reused across invocations)
const gemini = new GeminiClient({
apiKey: process.env.GOOGLE_AI_STUDIO_KEY!,
model: process.env.GEMINI_MODEL || "gemini-3-flash-preview",
});
const tools = new ToolRegistry();
const agent = new MyAgent(tools, { gemini });
// Framework handler
const handler = createAgentExecuteHandler({
resolveAgent: (agentType) => {
if (agentType === "my-agent") return agent;
throw new Error(`Unknown agentType: ${agentType}`);
},
getUserId: async (event) => {
// Extract user ID from auth header, token, etc.
return 1;
},
cors: { origin: "*" },
});
// Export the Cloud Function entry point
export const api = gcpHttp(handler);
Streaming (SSE)¶
For streaming responses (used by AgentChat with the SSE backend), use the dedicated stream endpoint factory:
import {
createGcpAgentExecuteStreamEndpoint,
} from "@modernpath/agent-framework";
export const apiStream = createGcpAgentExecuteStreamEndpoint({
resolveAgent: (agentType) => {
if (agentType === "my-agent") return agent;
throw new Error(`Unknown agentType: ${agentType}`);
},
getUserId: async () => 1,
cors: { origin: "*" },
});
createGcpAgentExecuteStreamEndpoint
This convenience factory composes createAgentExecuteStreamHandler with the GCP SSE adapter internally. It handles writing SSE chunks to the Express response and calling res.end() when the stream completes.
Combined Entry Point¶
In practice, you often need a single Cloud Function that handles multiple routes (execute, stream, health, admin). Use a router pattern:
import {
createAgentExecuteHandler,
createAgentExecuteStreamHandler,
gcpHttp,
} from "@modernpath/agent-framework";
// ... (build agent, tools, gemini as above)
const executeHandler = createAgentExecuteHandler({ resolveAgent, getUserId, cors });
const streamHandler = createAgentExecuteStreamHandler({ resolveAgent, getUserId, cors });
export const api = async (req: any, res: any) => {
// Set CORS headers
res.set("access-control-allow-origin", "*");
res.set("access-control-allow-headers", "content-type, authorization");
res.set("access-control-allow-methods", "POST, OPTIONS");
if (req.method === "OPTIONS") return res.status(204).send("");
const pathname = String(req.path || "/");
// Health check
if (pathname === "/health") {
return res.status(200).send(JSON.stringify({ ok: true }));
}
// Route: /api/agents/:auditingId/execute/stream
if (pathname.includes("/execute/stream")) {
// ... wire streamHandler with SSE writing
}
// Route: /api/agents/:auditingId/execute
if (pathname.includes("/execute")) {
const event = toHttpEvent(req); // (1)
const result = await executeHandler(event);
res.status(result.statusCode);
for (const [k, v] of Object.entries(result.headers || {})) res.set(k, v);
return res.send(result.body);
}
return res.status(404).send(JSON.stringify({ error: "Not found" }));
};
toHttpEventconverts the Express request into the framework'sHttpEventtype. See the Handler Factories reference for the full interface.
Bundling with esbuild¶
Cloud Functions 2nd gen supports ESM modules. Use esbuild to produce a self-contained bundle:
{
"scripts": {
"build:gcp": "rm -rf dist-gcp && mkdir -p dist-gcp && esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist-gcp/index.mjs --external:@google-cloud/firestore && cp package.gcp.json dist-gcp/package.json"
},
"devDependencies": {
"esbuild": "^0.25.0"
}
}
{
"scripts": {
"build:gcp": "rm -rf dist-gcp && mkdir -p dist-gcp && esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist-gcp/index.cjs --external:@google-cloud/firestore && cp package.gcp.json dist-gcp/package.json"
},
"devDependencies": {
"esbuild": "^0.25.0"
}
}
Create a minimal package.json for the deployment artifact:
{
"name": "my-agent-gcp",
"version": "1.0.0",
"type": "module",
"main": "index.mjs",
"dependencies": {
"@google-cloud/firestore": "^7.0.0"
}
}
Non-bundleable dependencies
@google-cloud/firestore uses native gRPC bindings and must be listed as an external in esbuild and as a dependency in the deployment package.json. If you do not use Firestore-backed stores, you can omit it entirely.
Including Static Assets¶
If your agent uses prompt templates (YAML files) or fixture data, copy them into the output directory:
Deploying¶
Using gcloud CLI¶
gcloud functions deploy my-agent \
--gen2 \
--runtime nodejs20 \
--region europe-west1 \
--source dist-gcp \
--entry-point api \
--trigger-http \
--allow-unauthenticated \
--memory 512Mi \
--timeout 300s \
--set-env-vars "GEMINI_MODEL=gemini-3-flash-preview,GCP_PROJECT_ID=my-project" \
--set-secrets "GOOGLE_AI_STUDIO_KEY=my-api-key-secret:latest"
Use Secret Manager for API keys
Never pass GOOGLE_AI_STUDIO_KEY as a plain environment variable. Use --set-secrets to mount it from Google Cloud Secret Manager at runtime.
Using Terraform¶
resource "google_cloudfunctions2_function" "agent" {
name = "my-agent"
location = "europe-west1"
build_config {
runtime = "nodejs20"
entry_point = "api"
source {
storage_source {
bucket = google_storage_bucket.source.name
object = google_storage_bucket_object.source_zip.name
}
}
}
service_config {
max_instance_count = 10
min_instance_count = 0
available_memory = "512Mi"
timeout_seconds = 300
environment_variables = {
GEMINI_MODEL = "gemini-3-flash-preview"
GCP_PROJECT_ID = var.project_id
}
secret_environment_variables {
key = "GOOGLE_AI_STUDIO_KEY"
project_id = var.project_id
secret = "my-api-key-secret"
version = "latest"
}
}
}
Required IAM Permissions¶
The Cloud Function's service account needs the following roles:
| Role | Purpose |
|---|---|
roles/aiplatform.user | Gemini API access (if using Vertex AI endpoint) |
roles/datastore.user | Firestore read/write (for FirestoreTraceStore, FirestoreConversationStore) |
roles/secretmanager.secretAccessor | Access secrets mounted via --set-secrets |
roles/storage.objectViewer | Read from GCS buckets (if using GCS-backed canonical grounding) |
roles/logging.logWriter | Write Cloud Logging entries (granted by default) |
Gemini via AI Studio vs. Vertex AI
If you use a GOOGLE_AI_STUDIO_KEY (API key from AI Studio), the aiplatform.user role is not required. The API key authenticates directly. The Vertex AI role is only needed when authenticating via service account credentials with the Vertex AI endpoint.
Environment Variables¶
Set these environment variables on your Cloud Function. See the full reference for all available variables.
# Required
GOOGLE_AI_STUDIO_KEY=<secret> # Gemini API key (use Secret Manager)
# Recommended
GEMINI_MODEL=gemini-3-flash-preview # Model name
GCP_PROJECT_ID=my-project # Enables Firestore-backed stores
# Optional
DEBUG=* # Enable debug logging
GROUNDING_MODE=chunks_only # Grounding policy
KB_STORE=fileSearchStores/my-store # Default knowledge base store
TRACE_CAPTURE_CONTENT=true # Capture full prompt/response in traces
Troubleshooting¶
Cold starts are slow
Set --min-instances 1 to keep at least one instance warm. This eliminates cold starts but incurs idle costs. For 2nd gen functions, cold starts are typically 1--3 seconds.
Function times out
The default timeout is 60 seconds. Agent executions with RAG retrieval and multiple tool calls can take longer. Increase with --timeout 300s (maximum 540 seconds for 2nd gen).
esbuild errors with @google-cloud/firestore
Always mark it as --external:@google-cloud/firestore. This package uses native Node.js bindings that esbuild cannot bundle. Include it in your deployment package.json instead.
CORS errors in the browser
Ensure you pass cors: { origin: "*" } (or your specific origin) to the handler factory. The gcpHttp adapter does not add CORS headers automatically -- the handler factory does.