diff --git a/docs/develop/python/index.mdx b/docs/develop/python/index.mdx index 59446aa08b..fe5df02a14 100644 --- a/docs/develop/python/index.mdx +++ b/docs/develop/python/index.mdx @@ -87,6 +87,7 @@ From there, you can dive deeper into any of the Temporal primitives to start bui - [Braintrust integration](/develop/python/integrations/braintrust) - [Google ADK integration](https://adk.dev/integrations/temporal/) +- [Google GenAI integration](/develop/python/integrations/google-genai) - [LangGraph integration](/develop/python/integrations/langgraph) - [LangSmith integration](/develop/python/integrations/langsmith) - [OpenAI Agents SDK integration](https://github.com/temporalio/sdk-python/blob/main/temporalio/contrib/openai_agents/README.md) diff --git a/docs/develop/python/integrations/google-genai.mdx b/docs/develop/python/integrations/google-genai.mdx new file mode 100644 index 0000000000..9990bc2a05 --- /dev/null +++ b/docs/develop/python/integrations/google-genai.mdx @@ -0,0 +1,656 @@ +--- +id: google-genai +title: Google GenAI integration +sidebar_label: Google GenAI +toc_max_heading_level: 2 +keywords: + - ai + - agents + - google genai + - gemini + - vertex ai +tags: + - Google GenAI + - Python SDK + - Temporal SDKs +description: + Call Google Gemini models durably from Python Workflows using the Temporal Python SDK and the Google GenAI plugin. +--- + +Temporal's Google GenAI integration lets you call [Gemini](https://ai.google.dev/gemini-api/docs) models from inside +Temporal Workflows, so a sequence of model calls keeps its place across Worker restarts, deploys, and transient failures. + +Temporal gives your code [Durable Execution](/temporal#durable-execution). The +[Google Gen AI SDK](https://googleapis.github.io/python-genai/) gives you the model API: content generation, automatic +function calling, chat sessions, structured output, files, and MCP. The integration connects the two so that you write +ordinary Gemini SDK code and run it as a Workflow, without writing your own retry loop or checkpointing. + +`GoogleGenAIPlugin` is what ties them together. You build a `genai.Client` with your credentials on the Worker and hand +it to the plugin. Inside the Workflow you construct a `TemporalAsyncClient`, which has the same shape as the SDK's async +client but routes every API call through a Temporal Activity. Each call gets its own timeout, retry policy, and Event +History entry, and your credentials stay on the Worker. + +import { ReleaseNoteHeader } from '@site/src/components'; + + + +Code snippets in this guide come from the +[Google GenAI plugin samples](https://github.com/temporalio/samples-python/tree/main/google_genai). Refer to the +samples for the complete code. + +## Prerequisites + +- This guide assumes you are already familiar with the Google Gen AI SDK. If you aren't, refer to the + [Gemini API documentation](https://ai.google.dev/gemini-api/docs) for more details. +- If you are new to Temporal, we recommend reading [Understanding Temporal](/evaluate/understanding-temporal) or taking + the [Temporal 101](https://learn.temporal.io/courses/temporal_101/) course. +- Ensure you have set up your local development environment by following the + [Set up your local development environment](/develop/python/set-up-your-local-python) guide. When you're done, leave + the Temporal development server running if you want to test your code locally. + +## Install the plugin + +Install the Temporal Python SDK with Google GenAI support (requires `temporalio` 1.31.0 or later): + +```bash +uv add "temporalio[google-genai]>=1.31.0" +``` + +If you use pip: + +```bash +pip install "temporalio[google-genai]>=1.31.0" +``` + +The [MCP](#use-mcp-tool-servers) path also needs the `mcp` package, which the extra does not install. + +## Call a model from a Workflow + +Construct a `TemporalAsyncClient` in your Workflow and call it the way you would call `genai.Client.aio`. The client +takes no credentials — it resolves each call to an Activity that runs on the Worker. + +{/* SNIPSTART python-google-genai-hello-world-workflow */} +[google_genai/hello_world/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/hello_world/workflow.py) +```py +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +@workflow.defn +class HelloWorldWorkflow: + @workflow.run + async def run(self, prompt: str) -> str: + client = TemporalAsyncClient() + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + ) + return response.text or "" + + +``` +{/* SNIPEND */} + +On the Worker, build a real `genai.Client` with your credentials, wrap it in a `GoogleGenAIPlugin`, and pass the plugin +to `Client.connect`. A Worker created from that Temporal Client picks up the plugin, which registers the Activity that +makes the API calls and swaps in the Pydantic payload converter that serializes Gemini types. + +{/* SNIPSTART python-google-genai-hello-world-worker */} +[google_genai/hello_world/run_worker.py](https://github.com/temporalio/samples-python/blob/main/google_genai/hello_world/run_worker.py) +```py +import asyncio +import os + +from google import genai +from temporalio.client import Client +from temporalio.contrib.google_genai import GoogleGenAIPlugin +from temporalio.worker import Worker + +from google_genai.hello_world.workflow import HelloWorldWorkflow + + +async def main() -> None: + # The real genai.Client (with credentials) lives only on the worker. + genai_client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"]) + plugin = GoogleGenAIPlugin(genai_client) + + client = await Client.connect( + os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"), + plugins=[plugin], + ) + + worker = Worker( + client, + task_queue="google-genai-hello-world", + workflows=[HelloWorldWorkflow], + ) + print("Worker started. Ctrl+C to exit.") + await worker.run() + + +if __name__ == "__main__": + asyncio.run(main()) +``` +{/* SNIPEND */} + +Because API calls run as Activities, the Worker process is the one that needs credentials. The samples use the Gemini +Developer API, which reads its key from the `GOOGLE_API_KEY` environment variable. + +```bash +export GOOGLE_API_KEY="your-api-key" +uv run google_genai/hello_world/run_worker.py +``` + +Start the Workflow the way you start any other Temporal Workflow. The starting Client does not need the plugin. + +{/* SNIPSTART python-google-genai-hello-world-run-workflow */} +[google_genai/hello_world/run_workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/hello_world/run_workflow.py) +```py +import asyncio +import os + +from temporalio.client import Client + +from google_genai.hello_world.workflow import HelloWorldWorkflow + + +async def main() -> None: + client = await Client.connect(os.environ.get("TEMPORAL_ADDRESS", "localhost:7233")) + + result = await client.execute_workflow( + HelloWorldWorkflow.run, + "Write a haiku about durable execution.", + id="google-genai-hello-world", + task_queue="google-genai-hello-world", + ) + + print(f"Result: {result}") + + +if __name__ == "__main__": + asyncio.run(main()) +``` +{/* SNIPEND */} + +## Run tools as Activities + +The Gemini SDK's automatic function calling loop runs inside the Workflow, so you don't write the tool loop yourself. +A tool can be either of the following: + +- A Temporal Activity wrapped with `activity_as_tool`. The model's call to it runs as its own Activity, with its own + timeout and retries, and appears in the Event History. Use this for anything that does I/O or is otherwise + non-deterministic. +- A plain Workflow method passed directly. It runs in the Workflow with no Activity dispatch, so it must be + [deterministic](/develop/python/workflows/basics#workflow-logic-requirements). + +This Workflow passes one of each on a single call. + +{/* SNIPSTART python-google-genai-tools-workflow */} +[google_genai/tools/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/tools/workflow.py) +```py +from datetime import timedelta + +from google.genai import types +from temporalio import activity, workflow +from temporalio.contrib.google_genai import TemporalAsyncClient, activity_as_tool +from temporalio.workflow import ActivityConfig + + +@activity.defn +async def get_weather(city: str) -> str: + """Look up the current weather for a city.""" + # Stub — replace with a real HTTP call in production. + return f"It's 72F and sunny in {city}." + + +@workflow.defn +class ToolsWorkflow: + @workflow.run + async def run(self, prompt: str) -> str: + client = TemporalAsyncClient() + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + config=types.GenerateContentConfig( + tools=[ + activity_as_tool( + get_weather, + activity_config=ActivityConfig( + start_to_close_timeout=timedelta(seconds=30), + ), + ), + self.recommend_activity, + ], + ), + ) + return response.text or "" + + async def recommend_activity(self, weather: str) -> str: + """Recommend something to do given a weather description.""" + if "sunny" in weather.lower(): + return "Go for a hike." + return "Visit a museum." + + +``` +{/* SNIPEND */} + +`activity_as_tool` keeps the wrapped function's name, docstring, and type signature, which is what the model uses to +decide when to call it. Its `activity_config` must set `start_to_close_timeout` or `schedule_to_close_timeout`; there is +no default, and the tool call fails without one. + +Register the Activity on the Worker alongside the Workflow. + +{/* SNIPSTART python-google-genai-tools-worker {"selectedLines": ["21-26"]} */} +[google_genai/tools/run_worker.py](https://github.com/temporalio/samples-python/blob/main/google_genai/tools/run_worker.py) +```py +worker = Worker( + client, + task_queue="google-genai-tools", + workflows=[ToolsWorkflow], + activities=[get_weather], +) +``` +{/* SNIPEND */} + +## Hold a multi-turn conversation + +`client.chats` works inside a Workflow. The chat session keeps its history in Workflow state, and each `send_message` +call runs as its own Activity, so a conversation that spans hours or days survives a Worker restart. + +{/* SNIPSTART python-google-genai-chat-workflow */} +[google_genai/chat/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/chat/workflow.py) +```py +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +@workflow.defn +class ChatWorkflow: + @workflow.run + async def run(self, prompts: list[str]) -> list[str]: + client = TemporalAsyncClient() + chat = client.chats.create(model="gemini-2.5-flash") + replies: list[str] = [] + for prompt in prompts: + response = await chat.send_message(prompt) + replies.append(response.text or "") + return replies + + +``` +{/* SNIPEND */} + +To drive the turns from the outside instead of from a list, take each prompt as a +[Signal](/develop/python/workflows/message-passing#signals) and wait on it with `workflow.wait_condition`. + +## Return structured output + +The plugin installs Temporal's Pydantic payload converter, so a Pydantic model passes through Temporal payloads +unchanged. Pass the model as `response_schema` and read the parsed result from `response.parsed`. + +{/* SNIPSTART python-google-genai-structured-output-workflow */} +[google_genai/structured_output/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/structured_output/workflow.py) +```py +from google.genai import types +from pydantic import BaseModel +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +class Recipe(BaseModel): + name: str + ingredients: list[str] + steps: list[str] + + +@workflow.defn +class StructuredOutputWorkflow: + @workflow.run + async def run(self, prompt: str) -> Recipe: + client = TemporalAsyncClient() + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + config=types.GenerateContentConfig( + response_mime_type="application/json", + response_schema=Recipe, + ), + ) + recipe = response.parsed + assert isinstance(recipe, Recipe) + return recipe + + +``` +{/* SNIPEND */} + +## Use MCP tool servers + +To give a model tools from an [MCP](https://modelcontextprotocol.io/) server, register the server on the Worker and +reference it by name in the Workflow. Connecting to an MCP server is external I/O, so the plugin holds the connection on +the Worker and runs `list_tools` and `call_tool` as Activities against it. + +Register each server with a factory that yields a connected, initialized `mcp.ClientSession`. + +{/* SNIPSTART python-google-genai-mcp-worker {"selectedLines": ["20-32"]} */} +[google_genai/mcp/run_worker.py](https://github.com/temporalio/samples-python/blob/main/google_genai/mcp/run_worker.py) +```py +@asynccontextmanager +async def echo_session() -> AsyncIterator[ClientSession]: + """Yield a connected, initialized session to the stdio echo MCP server.""" + params = StdioServerParameters(command=sys.executable, args=[ECHO_SERVER]) + async with stdio_client(params) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + yield session + + +async def main() -> None: + genai_client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"]) + plugin = GoogleGenAIPlugin(genai_client, mcp_servers={"echo": echo_session}) +``` +{/* SNIPEND */} + +In the Workflow, pass a `TemporalMcpClientSession` with the same name in the `tools` list. Automatic function calling +discovers and calls the server's tools from there. + +{/* SNIPSTART python-google-genai-mcp-workflow */} +[google_genai/mcp/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/mcp/workflow.py) +```py +from datetime import timedelta + +from google.genai import types +from temporalio import workflow +from temporalio.contrib.google_genai import ( + TemporalAsyncClient, + TemporalMcpClientSession, +) +from temporalio.workflow import ActivityConfig + + +@workflow.defn +class McpWorkflow: + @workflow.run + async def run(self, prompt: str) -> str: + client = TemporalAsyncClient() + session = TemporalMcpClientSession( + "echo", + cache_tools=True, + activity_config=ActivityConfig( + start_to_close_timeout=timedelta(seconds=30), + ), + ) + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + config=types.GenerateContentConfig(tools=[session]), + ) + return response.text or "" + + +``` +{/* SNIPEND */} + +`cache_tools=True` reuses the first `list_tools` result for the rest of the run instead of listing tools before every +call. The Worker keeps each MCP connection open between uses and disconnects it after five minutes idle; change that +with `mcp_connection_idle_timeout` on the plugin. + +Server-side MCP needs no wiring. Vertex AI's `Tool(mcp_servers=[McpServer(...)])` and the Interactions API's MCP steps +run on Google's backend, so they pass through as ordinary request and response data. + +## Stream model output + +`generate_content_stream` can forward chunks to an external subscriber while the Workflow is still running. Set +`streaming_topic` on the client and host a `WorkflowStream` in the Workflow's `@workflow.init`; each chunk is published +to that topic as it arrives. The Workflow's own iteration over the stream is unchanged. + +{/* SNIPSTART python-google-genai-streaming-workflow */} +[google_genai/streaming/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/streaming/workflow.py) +```py +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient +from temporalio.contrib.workflow_streams import WorkflowStream + + +@workflow.defn +class StreamingWorkflow: + @workflow.init + def __init__(self, prompt: str) -> None: + # Hosting a WorkflowStream is required when streaming_topic is set. + self.stream = WorkflowStream() + self._done = False + + @workflow.run + async def run(self, prompt: str) -> str: + client = TemporalAsyncClient(streaming_topic="gemini") + chunks: list[str] = [] + async for chunk in await client.models.generate_content_stream( + model="gemini-2.5-flash", + contents=prompt, + ): + chunks.append(chunk.text or "") + await workflow.wait_condition(lambda: self._done) + return "".join(chunks) + + @workflow.signal + def finish(self) -> None: + self._done = True + + +``` +{/* SNIPEND */} + +A consumer subscribes to the topic with `WorkflowStreamClient`. The published chunks are Pydantic +`GenerateContentResponse` objects, so the subscribing Client needs `pydantic_data_converter`. + +{/* SNIPSTART python-google-genai-streaming-run-workflow {"selectedLines": ["29-42"]} */} +[google_genai/streaming/run_workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/streaming/run_workflow.py) +```py +# Subscribe to the "gemini" topic and print chunks as the model produces them. +stream = WorkflowStreamClient.create(client, workflow_id) +async for item in stream.subscribe( + ["gemini"], + from_offset=0, + result_type=types.GenerateContentResponse, + poll_cooldown=timedelta(milliseconds=50), +): + chunk: types.GenerateContentResponse = item.data + if chunk.text: + print(chunk.text, end="", flush=True) + if chunk.candidates and chunk.candidates[0].finish_reason: + print() + break +``` +{/* SNIPEND */} + +The streaming Activity batches published chunks and flushes them every 100 milliseconds by default. Adjust that with +`streaming_batch_interval`. Delivery is at-least-once per Activity attempt: if the streaming Activity retries, the model +call re-runs and republishes, so subscribers should tolerate duplicates and treat the Workflow result as the source of +truth. + +## Upload files and reference them in a prompt + +`client.files` runs as Activities too, so the file is read on the Worker rather than in the Workflow. Upload it, then +pass the returned handle in `contents`. + +{/* SNIPSTART python-google-genai-files-workflow */} +[google_genai/files/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/files/workflow.py) +```py +from typing import cast + +from google.genai import types +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +@workflow.defn +class FilesWorkflow: + @workflow.run + async def run(self, file_path: str, prompt: str) -> str: + client = TemporalAsyncClient() + uploaded = await client.files.upload( + file=file_path, + config=types.UploadFileConfig(mime_type="text/plain"), + ) + contents = cast(types.ContentListUnion, [prompt, uploaded]) + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=contents, + ) + return response.text or "" + + +``` +{/* SNIPEND */} + +The file path resolves on the Worker, so the Worker needs access to it. Operations that require separate Google Cloud +credentials, such as `files.register_files`, use the `extra_credentials` you pass to the plugin. + +## Use the Interactions API and managed agents + +`client.interactions` and `client.agents` are server-managed: the state lives on Google's backend and each operation +runs as its own Activity. + +{/* SNIPSTART python-google-genai-interactions-workflow */} +[google_genai/interactions/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/interactions/workflow.py) +```py +from typing import Any + +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +@workflow.defn +class InteractionsWorkflow: + @workflow.run + async def run(self, prompt: str) -> dict[str, Any]: + client = TemporalAsyncClient() + + # create/get return either an Interaction or a streaming response; without + # stream=True the result is always an Interaction. + interaction: Any = await client.interactions.create( + model="gemini-2.5-flash", + input=prompt, + ) + fetched: Any = await client.interactions.get(interaction.id) + await client.interactions.delete(interaction.id) + + return {"id": interaction.id, "status": str(fetched.status)} + + +``` +{/* SNIPEND */} + +Two limits apply to the Interactions API: + +- It has no automatic function calling. Declare tools as `{"type": "function", ...}` dicts and drive the tool loop + yourself, running each call with `workflow.execute_activity` or an `activity_as_tool` callable. +- Streamed interactions are batched. The Activity drains the server-sent event stream and the Workflow iterates the + collected events. + +`client.webhooks` is not supported in Workflows. + +## Run against Vertex AI + +To use Vertex AI instead of the Gemini Developer API, set `vertexai=True` on both sides. In the Workflow, pass the +project and location as Workflow arguments rather than reading environment variables, which keeps the Workflow +deterministic. + +{/* SNIPSTART python-google-genai-vertex-ai-workflow */} +[google_genai/vertex_ai/workflow.py](https://github.com/temporalio/samples-python/blob/main/google_genai/vertex_ai/workflow.py) +```py +from temporalio import workflow +from temporalio.contrib.google_genai import TemporalAsyncClient + + +@workflow.defn +class VertexAIWorkflow: + @workflow.run + async def run(self, prompt: str, project: str, location: str) -> str: + client = TemporalAsyncClient( + vertexai=True, + project=project, + location=location, + ) + response = await client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + ) + return response.text or "" + + +``` +{/* SNIPEND */} + +The Worker's `genai.Client` uses Application Default Credentials instead of an API key. Run +`gcloud auth application-default login`, or set `GOOGLE_APPLICATION_CREDENTIALS` to a service account key file. + +{/* SNIPSTART python-google-genai-vertex-ai-worker {"selectedLines": ["13-18"]} */} +[google_genai/vertex_ai/run_worker.py](https://github.com/temporalio/samples-python/blob/main/google_genai/vertex_ai/run_worker.py) +```py +genai_client = genai.Client( + vertexai=True, + project=os.environ["GOOGLE_CLOUD_PROJECT"], + location=os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1"), +) +plugin = GoogleGenAIPlugin(genai_client) +``` +{/* SNIPEND */} + +The `vertexai` setting must match on both sides. A Workflow that sets `vertexai=True` against a Worker configured for +the Gemini Developer API sends requests the backend can't serve. + +## Set timeouts and retries + +Every API call defaults to a 60-second `start_to_close_timeout` and Temporal's default retry policy. Override that for +all of a client's calls with `activity_config`: + +```python +from datetime import timedelta + +from temporalio.common import RetryPolicy +from temporalio.contrib.google_genai import TemporalAsyncClient +from temporalio.workflow import ActivityConfig + +client = TemporalAsyncClient( + activity_config=ActivityConfig( + start_to_close_timeout=timedelta(minutes=5), + retry_policy=RetryPolicy(maximum_attempts=3), + ), +) +``` + +`activity_as_tool` and `TemporalMcpClientSession` take their own `activity_config`, so tool calls and MCP calls can use +different limits than model calls. + +Let Temporal own retries. The plugin rejects a `genai.Client` configured with `http_options.retry_options`, because an +SDK-internal retry loop hides its attempts inside a single Activity and compounds with the Temporal retry policy. + +## Samples + +The [Google GenAI plugin samples](https://github.com/temporalio/samples-python/tree/main/google_genai) cover each +pattern as a self-contained, runnable scenario: + +- [`hello_world`](https://github.com/temporalio/samples-python/tree/main/google_genai/hello_world): one + `generate_content` call. +- [`tools`](https://github.com/temporalio/samples-python/tree/main/google_genai/tools): an Activity tool and a + Workflow-method tool on the same call. +- [`streaming`](https://github.com/temporalio/samples-python/tree/main/google_genai/streaming): chunks forwarded + to an external subscriber with `WorkflowStream`. +- [`chat`](https://github.com/temporalio/samples-python/tree/main/google_genai/chat): a multi-turn conversation + with `client.chats`. +- [`structured_output`](https://github.com/temporalio/samples-python/tree/main/google_genai/structured_output): + typed JSON output through a Pydantic model. +- [`mcp`](https://github.com/temporalio/samples-python/tree/main/google_genai/mcp): MCP tools run as Activities, + with a self-contained echo server. +- [`files`](https://github.com/temporalio/samples-python/tree/main/google_genai/files): a file uploaded with + `client.files` and referenced in a prompt. +- [`interactions`](https://github.com/temporalio/samples-python/tree/main/google_genai/interactions): + server-managed conversations with `client.interactions`. +- [`agents`](https://github.com/temporalio/samples-python/tree/main/google_genai/agents): managed agent create, + get, list, and delete with `client.agents`. +- [`vertex_ai`](https://github.com/temporalio/samples-python/tree/main/google_genai/vertex_ai): the same + hello-world flow against Vertex AI. diff --git a/sidebars.js b/sidebars.js index cb8da6f78d..7a5f5b90df 100644 --- a/sidebars.js +++ b/sidebars.js @@ -649,6 +649,7 @@ const developPythonCategory = { items: [ 'develop/python/integrations/braintrust', 'develop/python/integrations/google-adk', + 'develop/python/integrations/google-genai', 'develop/python/integrations/langgraph', 'develop/python/integrations/langsmith', 'develop/python/integrations/strands-agents', diff --git a/src/components/IntegrationsGrid/integrations-data.json b/src/components/IntegrationsGrid/integrations-data.json index 6e741d91b9..5e5f29fd31 100644 --- a/src/components/IntegrationsGrid/integrations-data.json +++ b/src/components/IntegrationsGrid/integrations-data.json @@ -71,6 +71,15 @@ "sdk": "Go", "href": "/develop/go/integrations/google-adk" }, + { + "name": "Google GenAI", + "description": "Call Google Gemini models durably from Temporal Workflows with the Google Gen AI SDK.", + "tags": [ + "Agent framework" + ], + "sdk": "Python", + "href": "/develop/python/integrations/google-genai" + }, { "name": "Grafana Cloud", "description": "Export Temporal Cloud metrics to Grafana Cloud with a serverless integration and pre-built dashboard.", diff --git a/vale/styles/Temporal/Headings.yml b/vale/styles/Temporal/Headings.yml index a49b5b6e75..6a1fb959bf 100644 --- a/vale/styles/Temporal/Headings.yml +++ b/vale/styles/Temporal/Headings.yml @@ -179,3 +179,8 @@ exceptions: - Terraform - Prometheus - Grafana + # AI vendor and model-platform proper nouns + - AI + - Google + - Gemini + - Vertex diff --git a/vale/test/headings-good.md b/vale/test/headings-good.md index 91d9c13320..d940775612 100644 --- a/vale/test/headings-good.md +++ b/vale/test/headings-good.md @@ -51,3 +51,9 @@ ## Configure mTLS and PrivateLink for AWS ## Worker Versioning and Patching + +## Run against Vertex AI + +## Call a Gemini model from a Workflow + +## Authenticate to Google Cloud