new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Temporal · Develop · all subjects

integrations

245 notes in this subject, read out of this brain and free to use. This is page 4 of 5.

Google ADK basic workflow example

Example of a basic Google ADK workflow with an Activity-backed tool: func AgentWorkflow(ctx workflow.Context, question string) (string, error) { weatherTool, err := googleadk.ActivityAsTool(GetWeather, googleadk.ActivityToolOptions{ Name: WeatherToolName, Description: "Get the current weather for a city.", }) if err != nil { return "", err } root, err := llmagent.New(llmagent.Config{ Name: "assistant", Description: "a helpful weather assistant", Model: googleadk.NewModel(ModelName), Instruction: "Answer the user's question. Use the get_weather tool when asked about the weather.", Tools: []tool.Tool{weatherTool}, }) if err != nil { return "", err } r, err := runner.New(runner.Config{ AppName: "weather", Agent: root, SessionService: session.InMemoryService(), AutoCreateSession: true, }) if err != nil { return "", err } adkCtx := googleadk.NewContext(ctx) msg := genai.NewContentFromText(question, genai.RoleUser) var answer string for ev, err := range r.Run(adkCtx, "user-1", "session-1", msg, agent.RunConfig{}) { if err != nil { return "", err } if ev != nil && ev.Content != nil { for _, p := range ev.Content.Parts { if p != nil && p.Text != "" { answer = p.Text } } } } return answer, nil }

Google ADK activity tool example

Example of a simple Activity tool for Google ADK: func GetWeather(ctx context.Context, in GetWeatherInput) (GetWeatherOutput, error) { return GetWeatherOutput{City: in.City, Conditions: "sunny, 72°F"}, nil }

Spring AI integration module for Java SDK

The Temporal Spring AI integration (io.temporal:temporal-spring-ai) makes Spring AI agents durable by running model calls through Temporal Activities recorded in Event history. Tools are dispatched per their type: Activity stubs and Nexus stubs as durable operations, @SideEffectTool classes wrapped in Workflow.sideEffect, and plain tools running directly in Workflow code. The integration is built on the Temporal Java SDK's Plugin system.

Spring AI integration prerequisites and minimum versions

The Temporal Spring AI integration requires: Java 17, Spring Boot 3.x, Spring AI 1.1.0, and Temporal Java SDK 1.35.0. The temporal-spring-boot-starter and a Spring AI model starter (such as spring-ai-starter-model-openai) are also required; temporal-spring-ai does not pull in a model provider on its own.

Auto-registered Activities with Spring AI integration

When temporal-spring-ai is on the classpath, the SpringAiPlugin auto-registers ChatModelActivity with all Temporal Workers created by the Spring Boot integration. Optional Activities are auto-configured when their dependencies are present: VectorStoreActivity (requires spring-ai-rag), EmbeddingModelActivity (requires spring-ai-rag), and McpClientActivity (requires spring-ai-mcp).

ActivityChatModel usage in workflows

Use ActivityChatModel as a Spring AI ChatModel inside a Workflow. Wrap ActivityChatModel in a TemporalChatClient to build prompts and register tools. ActivityChatModel.forDefault() resolves to the default Spring AI ChatModel bean. To target a specific model in a multi-model application, pass its bean name to ActivityChatModel.forModel("modelName"). Every call goes through a Temporal Activity, so model responses are durable and retried per Activity options.

Activity stubs as Spring AI tools

An interface annotated with both @ActivityInterface and Spring AI @Tool methods is auto-detected and executed as a Temporal Activity. Use this for external calls that need retries and timeouts. Methods are decorated with @Tool (making them available to the AI model), @ActivityMethod, and @ToolParam annotations for parameters.

@SideEffectTool for non-deterministic operations

Classes annotated with @SideEffectTool have each @Tool method wrapped in Workflow.sideEffect(). The result is recorded in history on first execution and replayed from history afterward. Use this for cheap, non-deterministic operations such as timestamps or UUIDs.

Plain tools in Spring AI integration

Any class with @Tool methods that isn't an Activity stub, Nexus stub, or @SideEffectTool runs directly on the Workflow thread. Use this for inherently deterministic tools (such as updating in-memory agent state), or for orchestration of durable primitives as needed, for example calling multiple Activities, child Workflows, wait conditions, or other Temporal durable primitives.

Nexus service stubs as Spring AI tools

Nexus service stubs with @Tool methods are auto-detected and invoked as Nexus operations, enabling cross-Namespace tool calls.

Default Activity options for ChatModelActivity

ActivityChatModel.forDefault() and forModel(name) build the chat Activity stub with sensible defaults: a 2-minute start-to-close timeout, 3 attempts, and org.springframework.ai.retry.NonTransientAiException and java.lang.IllegalArgumentException classified as non-retryable so a bad API key or invalid prompt fails fast.

Custom Activity options for chat models

Pass an ActivityOptions directly to ActivityChatModel when you need finer control such as a specific Task Queue, heartbeats, priority, or a custom RetryOptions. For configuration-driven per-model overrides, declare a ChatModelActivityOptions bean. Use the special key ChatModelTypes.DEFAULT_MODEL_NAME (the literal "default") as a global catch-all that applies to any model not explicitly listed.

Provider-specific chat options pass-through

Provider-specific ChatOptions subclasses (such as AnthropicChatOptions or OpenAiChatOptions) pass through the Activity boundary unchanged. Attach them via ChatClient.defaultOptions(...) and the plugin re-applies them on the Activity side before calling the underlying model. This relies on the ChatOptions subclass overriding copy() to return its own type.

Media in chat messages and 1 MiB default cap

Prefer URI-based media when attaching images, audio, or other binary content to chat messages. Raw byte[] media gets serialized into every chat Activity's input and result payload, which end up inside Temporal Event history events. Server-side history events have a fixed 2 MiB size limit; the plugin enforces a 1 MiB default cap on inline bytes and fails fast with a non-retryable ApplicationFailure pointing at the URI alternative. Override the cap by setting the system property io.temporal.springai.maxMediaBytes before your worker starts (positive integer; 0 disables the check).

ActivityMcpClient for MCP tool calls

ActivityMcpClient.create() and create(ActivityOptions) work the same way as ActivityChatModel for MCP tool calls, with a 30-second default timeout. ActivityMcpClient wraps a Spring AI MCP client so that remote MCP tool calls become durable Activity executions.

Vector stores, embeddings, and MCP auto-registration

When the corresponding Spring AI modules are on the classpath, the integration registers Activities for vector stores, embeddings, and MCP tool calls. Inject the matching Spring AI types into your Activities or Workflows and use them as you would in any Spring AI application. You can also register these plugins explicitly using: new VectorStorePlugin(vectorStore), new EmbeddingModelPlugin(embeddingModel), and new McpPlugin().

Spring AI integration streaming not supported

Streaming responses are not currently supported in the Temporal Spring AI integration.

Spring AI integration custom Activity options example

Example of passing custom ActivityOptions to ActivityChatModel for a specific Task Queue: ActivityChatModel chatModel = ActivityChatModel.forDefault( ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) .setTaskQueue("chat-heavy") .build());

Spring AI integration URI-based media example

Example of preferred URI-based media attachment for images: Media image = new Media(MimeTypeUtils.IMAGE_PNG, URI.create("https://cdn.example.com/pic.png"));

Spring AI integration example: TimestampTools with @SideEffectTool

Example of @SideEffectTool class with non-deterministic operations wrapped in sideEffect(): @SideEffectTool public class TimestampTools { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.systemDefault()); @Tool(description = "Get the current date and time") public String getCurrentDateTime() { return FORMATTER.format(Instant.now()); } @Tool(description = "Get the current Unix timestamp in milliseconds") public long getCurrentTimestamp() { return System.currentTimeMillis(); } @Tool(description = "Generate a random UUID") public String generateUuid() { return UUID.randomUUID().toString(); } @Tool(description = "Get the current date and time in a specific timezone") public String getDateTimeInTimezone( @ToolParam(description = "Timezone ID (e.g., 'America/New_York', 'UTC', 'Europe/London')") String timezone) { try { ZoneId zoneId = ZoneId.of(timezone); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(zoneId); return formatter.format(Instant.now()); } catch (Exception e) { return "Invalid timezone: " + timezone + ". Use formats like 'America/New_York' or 'UTC'."; } } }

Spring AI integration example: StringTools as plain workflow tool

Example of deterministic plain tool class that runs directly on the Workflow thread: public class StringTools { @Tool(description = "Reverse a string, returning the characters in opposite order") public String reverse(@ToolParam(description = "The string to reverse") String input) { if (input == null) { return null; } return new StringBuilder(input).reverse().toString(); } @Tool(description = "Count the number of words in a text") public int countWords(@ToolParam(description = "The text to count words in") String text) { if (text == null || text.isBlank()) { return 0; } return text.trim().split("\\s+").length; } @Tool(description = "Convert text to all uppercase letters") public String toUpperCase(@ToolParam(description = "The text to convert") String text) { if (text == null) { return null; } return text.toUpperCase(java.util.Locale.ROOT); } @Tool(description = "Convert text to all lowercase letters") public String toLowerCase(@ToolParam(description = "The text to convert") String text) { if (text == null) { return null; } return text.toLowerCase(java.util.Locale.ROOT); } @Tool(description = "Check if a string is a palindrome (reads the same forwards and backwards)") public boolean isPalindrome(@ToolParam(description = "The text to check") String text) { if (text == null) { return false; } String normalized = text.toLowerCase(java.util.Locale.ROOT).replaceAll("\\s+", ""); String reversed = new StringBuilder(normalized).reverse().toString(); return normalized.equals(reversed); } }

Spring AI integration example: WeatherActivity as Activity tool

Example of Activity stub with Spring AI @Tool methods: @ActivityInterface public interface WeatherActivity { @Tool( description = "Get the current weather for a city. Returns temperature, conditions, and humidity.") @ActivityMethod String getWeather( @ToolParam(description = "The name of the city (e.g., 'Seattle', 'New York')") String city); @Tool(description = "Get the weather forecast for a city for the specified number of days.") @ActivityMethod String getForecast( @ToolParam(description = "The name of the city") String city, @ToolParam(description = "Number of days to forecast (1-7)") int days); }

Spring AI integration example: Creating TemporalChatClient with mixed tools

Example of building a TemporalChatClient with Activity stubs, plain workflow tools, and @SideEffectTool classes: @WorkflowInit public ChatWorkflowImpl(String systemPrompt) { ActivityChatModel activityChatModel = ActivityChatModel.forDefault(); WeatherActivity weatherTool = Workflow.newActivityStub( WeatherActivity.class, ActivityOptions.newBuilder() .setStartToCloseTimeout(Duration.ofSeconds(30)) .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(3).build()) .build()); StringTools stringTools = new StringTools(); TimestampTools timestampTools = new TimestampTools(); ChatMemory chatMemory = MessageWindowChatMemory.builder() .chatMemoryRepository(new InMemoryChatMemoryRepository()) .maxMessages(20) .build(); this.chatClient = TemporalChatClient.builder(activityChatModel) .defaultSystem(systemPrompt) .defaultTools(weatherTool, stringTools, timestampTools) .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) .build(); }

Spring AI integration example: Custom Activity options per model

Example of declaring a ChatModelActivityOptions bean for per-model Activity option overrides: @Bean public ChatModelActivityOptions chatModelActivityOptions() { return new ChatModelActivityOptions( Map.of( "anthropicChatModel", ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) .setStartToCloseTimeout(Duration.ofMinutes(5)) .setScheduleToCloseTimeout(Duration.ofMinutes(15)) .build())); }

Spring AI integration example: Provider-specific chat options

Example of using provider-specific ChatOptions like AnthropicChatOptions with extended thinking: AnthropicChatOptions thinkingOptions = AnthropicChatOptions.builder() .thinking(AnthropicApi.ThinkingType.ENABLED, 1024) .temperature(1.0) .maxTokens(4096) .build(); chatClients.put( "think", TemporalChatClient.builder(anthropicModel) .defaultSystem( "You are a helpful assistant powered by Anthropic with extended thinking. " + "Use the thinking budget to reason carefully, then give a crisp answer " + "that reflects the reasoning you did.") .defaultOptions(thinkingOptions) .build());

Spring AI integration Maven dependency

To add the Spring AI integration, include the temporal-spring-ai dependency alongside temporal-spring-boot-starter and a Spring AI model starter: <dependency> <groupId>io.temporal</groupId> <artifactId>temporal-spring-ai</artifactId> <version>${temporal-sdk.version}</version> </dependency>

Spring AI integration Gradle dependency

To add the Spring AI integration with Gradle Groovy DSL: implementation "io.temporal:temporal-spring-ai:${temporalSdkVersion}"

GoogleGenAIPlugin wraps genai.Client for Workflows

GoogleGenAIPlugin ties the Google Gen AI SDK to Temporal by wrapping a real genai.Client with credentials. You build a genai.Client with credentials on the Worker and pass it to GoogleGenAIPlugin, then pass the plugin to Client.connect. The plugin registers Activities for API calls and swaps in the Pydantic payload converter that serializes Gemini types.

TemporalAsyncClient routes Gemini API calls through Activities

Inside a Workflow, construct a TemporalAsyncClient instead of genai.Client. It 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 credentials stay on the Worker.

GoogleGenAI plugin minimum version requirement

The GoogleGenAI integration requires temporalio version 1.31.0 or later. Install with uv add "temporalio[google-genai]>=1.31.0" or pip install "temporalio[google-genai]>=1.31.0".

MCP package installation for MCP tool servers

To use MCP tool servers with GoogleGenAI, the mcp package must be installed separately; the google-genai extra does not include it.

activity_as_tool wraps Activities as Gemini tools

Use activity_as_tool to wrap a Temporal Activity so the Gemini model can call it. The wrapped function keeps its name, docstring, and type signature, which the model uses to decide when to call it. The activity_config parameter must set start_to_close_timeout or schedule_to_close_timeout; there is no default and the tool call fails without one.

Plain Workflow methods as deterministic Gemini tools

A plain Workflow method can be passed directly to Gemini as a tool without wrapping with activity_as_tool. It runs in the Workflow with no Activity dispatch, so it must be deterministic. This is useful for lightweight decisions that don't do I/O.

Multi-turn conversations with client.chats in Workflows

client.chats works inside a Workflow and keeps chat history in Workflow state. Each send_message call runs as its own Activity, so a multi-turn conversation spanning hours or days survives a Worker restart.

Structured output via Pydantic models in GoogleGenAI

The plugin installs Temporal's Pydantic payload converter, so Pydantic models pass through Temporal payloads unchanged. Pass the model as response_schema to generate_content and read the parsed result from response.parsed. If response.parsed is None, the model returned malformed JSON.

MCP server registration with GoogleGenAIPlugin

Register MCP servers by passing mcp_servers dict to GoogleGenAIPlugin, where keys are server names and values are async context manager factories that yield connected, initialized mcp.ClientSession objects. The plugin holds connections on the Worker and runs list_tools and call_tool as Activities.

TemporalMcpClientSession for Workflow MCP access

In a Workflow, pass TemporalMcpClientSession with the server name to the tools list. Automatic function calling discovers and calls the server's tools from there. Set cache_tools=True to reuse the first list_tools result instead of listing before every call.

MCP connection idle timeout configuration

The Worker keeps each MCP connection open between uses and disconnects it after five minutes idle by default. Change this with mcp_connection_idle_timeout parameter on the GoogleGenAIPlugin.

Streaming model output with WorkflowStream

Set streaming_topic on TemporalAsyncClient and host a WorkflowStream in the Workflow's @workflow.init to forward chunks to an external subscriber while the Workflow runs. Each chunk is published to the topic as it arrives. The Workflow's own iteration over the stream is unchanged.

WorkflowStreamClient subscribes to model output streams

A consumer subscribes to streaming topics with WorkflowStreamClient.create(client, workflow_id). Published chunks are Pydantic GenerateContentResponse objects, so the subscribing Client needs pydantic_data_converter.

Streaming Activity batching and delivery semantics

The streaming Activity batches published chunks and flushes them every 100 milliseconds by default; adjust with streaming_batch_interval. Delivery is at-least-once per Activity attempt: if the streaming Activity retries, chunks are republished, so subscribers should tolerate duplicates and treat the Workflow result as the source of truth.

File upload and reference in GoogleGenAI Workflows

client.files runs as Activities, so files are read on the Worker rather than in the Workflow. Upload a file with client.files.upload, then pass the returned handle in contents for generate_content. The file path resolves on the Worker.

Extra credentials for GoogleGenAI operations

Operations requiring separate Google Cloud credentials, such as files.register_files, use the extra_credentials parameter passed to GoogleGenAIPlugin.

client.interactions and client.agents are server-managed

client.interactions and client.agents are server-managed: state lives on Google's backend and each operation runs as its own Activity. create/get return either an Interaction or a streaming response; without stream=True the result is always an Interaction.

Interactions API limitations in Workflows

The Interactions API 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.

Vertex AI integration with GoogleGenAI

To use Vertex AI instead of Gemini Developer API, set vertexai=True on both the Workflow's TemporalAsyncClient and the Worker's genai.Client. In the Workflow, pass project and location as arguments rather than reading environment variables to keep the Workflow deterministic. The Worker uses Application Default Credentials instead of an API key.

Vertex AI credentials configuration

For Vertex AI, run gcloud auth application-default login or set GOOGLE_APPLICATION_CREDENTIALS to a service account key file. The genai.Client is created with vertexai=True, project, and location parameters.

vertexai setting must match between Workflow and Worker

The vertexai setting must match on both the Workflow's TemporalAsyncClient and the Worker's genai.Client. A Workflow with vertexai=True against a Worker configured for Gemini Developer API sends requests the backend cannot serve.

Default timeouts and retries for GoogleGenAI API calls

Every GoogleGenAI API call defaults to a 60-second start_to_close_timeout and Temporal's default retry policy. Override for all client calls with activity_config parameter on TemporalAsyncClient.

Setting timeouts and retries via activity_config

Pass ActivityConfig with start_to_close_timeout and retry_policy to TemporalAsyncClient to set timeouts and retries for all API calls. activity_as_tool and TemporalMcpClientSession take their own activity_config, allowing tool and MCP calls to use different limits than model calls.

Do not use genai.Client http_options.retry_options with Temporal

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. Let Temporal own retries.

Deterministic Workflow requirement for GoogleGenAI

Workflows must remain deterministic. Use Activities for I/O-bound operations and non-deterministic logic. Plain Workflow methods used as Gemini tools must be deterministic.

Client credentials not needed for Workflow invocation

The starting Client that invokes the Workflow does not need the GoogleGenAIPlugin or credentials. Credentials and the plugin are only needed on the Worker.

HelloWorldWorkflow example with TemporalAsyncClient

A simple Workflow that generates content: @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 ""

Worker setup with GoogleGenAIPlugin example

import asyncio, os; from google import genai; from temporalio.client import Client; from temporalio.contrib.google_genai import GoogleGenAIPlugin; from temporalio.worker import 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]); await worker.run()

Activity tool configuration with activity_as_tool

When wrapping an Activity as a tool with activity_as_tool, the activity_config parameter must set start_to_close_timeout or schedule_to_close_timeout. Example: activity_as_tool(get_weather, activity_config=ActivityConfig(start_to_close_timeout=timedelta(seconds=30)))

Structured output Workflow with Recipe model

from pydantic import BaseModel; 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; if not isinstance(recipe, Recipe): raise ApplicationError(f"Gemini did not return a valid Recipe: {response.text!r}", non_retryable=True); return recipe

MCP workflow session example

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]))

Chat Workflow multi-turn conversation example

@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

Streaming Workflow with WorkflowStream

@workflow.defn class StreamingWorkflow: @workflow.init def __init__(self, prompt: str) -> None: 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 ""); try: await workflow.wait_condition(lambda: self._done, timeout=FINISH_TIMEOUT); except asyncio.TimeoutError: workflow.logger.warning("No finish signal after %s; completing without a subscriber.", FINISH_TIMEOUT); return "".join(chunks); @workflow.signal def finish(self) -> None: self._done = True

Give your agent this brain