@@ -74,10 +74,6 @@ takes no credentials — it resolves each call to an Activity that runs on the W
7474{ /* SNIPSTART python-google-genai-hello-world-workflow */ }
7575[ google_genai/hello_world/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/hello_world/workflow.py )
7676``` py
77- from temporalio import workflow
78- from temporalio.contrib.google_genai import TemporalAsyncClient
79-
80-
8177@workflow.defn
8278class HelloWorldWorkflow :
8379 @workflow.run
@@ -190,21 +186,6 @@ This Workflow passes one of each on a single call.
190186{ /* SNIPSTART python-google-genai-tools-workflow */ }
191187[ google_genai/tools/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/tools/workflow.py )
192188``` py
193- from datetime import timedelta
194-
195- from google.genai import types
196- from temporalio import activity, workflow
197- from temporalio.contrib.google_genai import TemporalAsyncClient, activity_as_tool
198- from temporalio.workflow import ActivityConfig
199-
200-
201- @activity.defn
202- async def get_weather (city : str ) -> str :
203- """ Look up the current weather for a city."""
204- # Stub — replace with a real HTTP call in production.
205- return f " It's 72F and sunny in { city} . "
206-
207-
208189@workflow.defn
209190class ToolsWorkflow :
210191 @workflow.run
@@ -221,13 +202,13 @@ class ToolsWorkflow:
221202 start_to_close_timeout = timedelta(seconds = 30 ),
222203 ),
223204 ),
224- self .recommend_activity ,
205+ self .recommend_thing_to_do ,
225206 ],
226207 ),
227208 )
228209 return response.text or " "
229210
230- async def recommend_activity (self , weather : str ) -> str :
211+ async def recommend_thing_to_do (self , weather : str ) -> str :
231212 """ Recommend something to do given a weather description."""
232213 if " sunny" in weather.lower():
233214 return " Go for a hike."
@@ -246,12 +227,13 @@ Register the Activity on the Worker alongside the Workflow.
246227{ /* SNIPSTART python-google-genai-tools-worker {"selectedLines": ["21-26"]} */ }
247228[ google_genai/tools/run_worker.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/tools/run_worker.py )
248229``` py
249- worker = Worker(
250- client,
251- task_queue = " google-genai-tools" ,
252- workflows = [ToolsWorkflow],
253- activities = [get_weather],
254- )
230+ # ...
231+ worker = Worker(
232+ client,
233+ task_queue = " google-genai-tools" ,
234+ workflows = [ToolsWorkflow],
235+ activities = [get_weather],
236+ )
255237```
256238{ /* SNIPEND */ }
257239
@@ -263,10 +245,6 @@ call runs as its own Activity, so a conversation that spans hours or days surviv
263245{ /* SNIPSTART python-google-genai-chat-workflow */ }
264246[ google_genai/chat/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/chat/workflow.py )
265247``` py
266- from temporalio import workflow
267- from temporalio.contrib.google_genai import TemporalAsyncClient
268-
269-
270248@workflow.defn
271249class ChatWorkflow :
272250 @workflow.run
@@ -294,12 +272,6 @@ unchanged. Pass the model as `response_schema` and read the parsed result from `
294272{ /* SNIPSTART python-google-genai-structured-output-workflow */ }
295273[ google_genai/structured_output/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/structured_output/workflow.py )
296274``` py
297- from google.genai import types
298- from pydantic import BaseModel
299- from temporalio import workflow
300- from temporalio.contrib.google_genai import TemporalAsyncClient
301-
302-
303275class Recipe (BaseModel ):
304276 name: str
305277 ingredients: list[str ]
@@ -320,7 +292,15 @@ class StructuredOutputWorkflow:
320292 ),
321293 )
322294 recipe = response.parsed
323- assert isinstance (recipe, Recipe)
295+ if not isinstance (recipe, Recipe):
296+ # ``parsed`` is None when the model returns malformed JSON. Fail the
297+ # workflow with an ApplicationError rather than asserting: an
298+ # assertion is a workflow task failure, which Temporal retries
299+ # forever, so the run would hang instead of failing visibly.
300+ raise ApplicationError(
301+ f " Gemini did not return a valid Recipe: { response.text!r } " ,
302+ non_retryable = True ,
303+ )
324304 return recipe
325305
326306
@@ -338,6 +318,7 @@ Register each server with a factory that yields a connected, initialized `mcp.Cl
338318{ /* SNIPSTART python-google-genai-mcp-worker {"selectedLines": ["20-32"]} */ }
339319[ google_genai/mcp/run_worker.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/mcp/run_worker.py )
340320``` py
321+ # ...
341322@asynccontextmanager
342323async def echo_session () -> AsyncIterator[ClientSession]:
343324 """ Yield a connected, initialized session to the stdio echo MCP server."""
@@ -360,17 +341,6 @@ discovers and calls the server's tools from there.
360341{ /* SNIPSTART python-google-genai-mcp-workflow */ }
361342[ google_genai/mcp/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/mcp/workflow.py )
362343``` py
363- from datetime import timedelta
364-
365- from google.genai import types
366- from temporalio import workflow
367- from temporalio.contrib.google_genai import (
368- TemporalAsyncClient,
369- TemporalMcpClientSession,
370- )
371- from temporalio.workflow import ActivityConfig
372-
373-
374344@workflow.defn
375345class McpWorkflow :
376346 @workflow.run
@@ -410,11 +380,6 @@ to that topic as it arrives. The Workflow's own iteration over the stream is unc
410380{ /* SNIPSTART python-google-genai-streaming-workflow */ }
411381[ google_genai/streaming/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/streaming/workflow.py )
412382``` py
413- from temporalio import workflow
414- from temporalio.contrib.google_genai import TemporalAsyncClient
415- from temporalio.contrib.workflow_streams import WorkflowStream
416-
417-
418383@workflow.defn
419384class StreamingWorkflow :
420385 @workflow.init
@@ -432,7 +397,15 @@ class StreamingWorkflow:
432397 contents = prompt,
433398 ):
434399 chunks.append(chunk.text or " " )
435- await workflow.wait_condition(lambda : self ._done)
400+ # Bound the wait: if the subscriber dies without signaling, complete
401+ # anyway instead of waiting forever.
402+ try :
403+ await workflow.wait_condition(lambda : self ._done, timeout = FINISH_TIMEOUT )
404+ except asyncio.TimeoutError:
405+ workflow.logger.warning(
406+ " No finish signal after %s ; completing without a subscriber." ,
407+ FINISH_TIMEOUT ,
408+ )
436409 return " " .join(chunks)
437410
438411 @workflow.signal
@@ -449,20 +422,21 @@ A consumer subscribes to the topic with `WorkflowStreamClient`. The published ch
449422{ /* SNIPSTART python-google-genai-streaming-run-workflow {"selectedLines": ["29-42"]} */ }
450423[ google_genai/streaming/run_workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/streaming/run_workflow.py )
451424``` py
452- # Subscribe to the "gemini" topic and print chunks as the model produces them.
453- stream = WorkflowStreamClient.create(client, workflow_id)
454- async for item in stream.subscribe(
455- [" gemini" ],
456- from_offset = 0 ,
457- result_type = types.GenerateContentResponse,
458- poll_cooldown = timedelta(milliseconds = 50 ),
459- ):
460- chunk: types.GenerateContentResponse = item.data
461- if chunk.text:
462- print (chunk.text, end = " " , flush = True )
463- if chunk.candidates and chunk.candidates[0 ].finish_reason:
464- print ()
465- break
425+ # ...
426+ if chunk.candidates and chunk.candidates[0 ].finish_reason:
427+ print ()
428+ return
429+
430+
431+ async def main () -> None :
432+ # The stream publishes Pydantic GenerateContentResponse chunks, so the
433+ # consumer needs the Pydantic data converter to decode them.
434+ client = await Client.connect(
435+ os.environ.get(" TEMPORAL_ADDRESS" , " localhost:7233" ),
436+ data_converter = pydantic_data_converter,
437+ )
438+ workflow_id = " google-genai-streaming"
439+
466440```
467441{ /* SNIPEND */ }
468442
@@ -479,13 +453,6 @@ pass the returned handle in `contents`.
479453{ /* SNIPSTART python-google-genai-files-workflow */ }
480454[ google_genai/files/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/files/workflow.py )
481455``` py
482- from typing import cast
483-
484- from google.genai import types
485- from temporalio import workflow
486- from temporalio.contrib.google_genai import TemporalAsyncClient
487-
488-
489456@workflow.defn
490457class FilesWorkflow :
491458 @workflow.run
@@ -517,12 +484,6 @@ runs as its own Activity.
517484{ /* SNIPSTART python-google-genai-interactions-workflow */ }
518485[ google_genai/interactions/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/interactions/workflow.py )
519486``` py
520- from typing import Any
521-
522- from temporalio import workflow
523- from temporalio.contrib.google_genai import TemporalAsyncClient
524-
525-
526487@workflow.defn
527488class InteractionsWorkflow :
528489 @workflow.run
@@ -562,10 +523,6 @@ deterministic.
562523{ /* SNIPSTART python-google-genai-vertex-ai-workflow */ }
563524[ google_genai/vertex_ai/workflow.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/vertex_ai/workflow.py )
564525``` py
565- from temporalio import workflow
566- from temporalio.contrib.google_genai import TemporalAsyncClient
567-
568-
569526@workflow.defn
570527class VertexAIWorkflow :
571528 @workflow.run
@@ -591,12 +548,13 @@ The Worker's `genai.Client` uses Application Default Credentials instead of an A
591548{ /* SNIPSTART python-google-genai-vertex-ai-worker {"selectedLines": ["13-18"]} */ }
592549[ google_genai/vertex_ai/run_worker.py] ( https://github.com/temporalio/samples-python/blob/main/google_genai/vertex_ai/run_worker.py )
593550``` py
594- genai_client = genai.Client(
595- vertexai = True ,
596- project = os.environ[" GOOGLE_CLOUD_PROJECT" ],
597- location = os.environ.get(" GOOGLE_CLOUD_LOCATION" , " us-central1" ),
598- )
599- plugin = GoogleGenAIPlugin(genai_client)
551+ # ...
552+ genai_client = genai.Client(
553+ vertexai = True ,
554+ project = os.environ[" GOOGLE_CLOUD_PROJECT" ],
555+ location = os.environ.get(" GOOGLE_CLOUD_LOCATION" , " us-central1" ),
556+ )
557+ plugin = GoogleGenAIPlugin(genai_client)
600558```
601559{ /* SNIPEND */ }
602560
0 commit comments