Deterministic Workflow

Learn how to create a deterministic multi-step workflow with agents.

Multi-Step Workflow Example

This example demonstrates how to create a deterministic workflow with agentic steps, combining native control flow and a "Reasoning and Acting" (ReAct) agent framework.

Unlike fully autonomous agent workflows, a deterministic workflow gives you control over the sequence of steps while leveraging the power of agents for specific tasks.

Python

multi-step-workflow.py

from oci.addons.adk import Agent, AgentClient
from custom_functon_tools import ResearcherToolkit, WriterToolkit

"""
This examples shows how you can build "deterministically orchestrated workflows with agentic steps".
"""

# Your (existing) vanilla python code to be integrated into this agentic workflow
def get_user_preferences():
    # Simulate result you fetched from a DB
    return {
        "email": "your@email.com",
        "style": ["casual", "humorous"],
        "topics": ["ai"]
    }

def main():

    client = AgentClient(
        auth_type="api_key",
        profile="DEFAULT",
        region="us-chicago-1"
    )

    researcher = Agent(
        client=client,
        agent_endpoint_id="ocid1.genaiagentendpoint...",
        name="Researcher",
        instructions="You are a researcher. You research trending keywords based on the user preferences.",
        tools=[ResearcherToolkit()]
    )

    writer = Agent(
        client=client,
        agent_endpoint_id="ocid1.genaiagentendpoint...",
        name="Writer",
        instructions="You are a writer. You write a blog post based on the trending keywords and the user preferences.",
        tools=[WriterToolkit()]
    )

    researcher.setup()
    writer.setup()

    # Step 1: Fetch user preferences or any pre-processing information. (non-agentic step)
    user_preferences = get_user_preferences()

    # Step 2: Research trending keywords using outputs from the previous steps as input. (agentic step)
    topics = user_preferences['topics']
    researcher_prompt = f"Research trending keywords for the following topics: {topics}"
    last_run_response = researcher.run(researcher_prompt)

    # Step 3: Write a blog post using outputs from last two steps as input. (agentic step) 
    keywords = last_run_response.output
    style = user_preferences['style']
    email = user_preferences['email']
    writer_prompt = f"Write a 5 sentences blog post and email it to {email}. Use style: {style}. Blog post should be based on: {keywords}."
    last_run_response = writer.run(writer_prompt)

    # Step 4: Do whatever you want with the last step output. Here we just print it.
    last_run_response.pretty_print()

if __name__ == "__main__":
    main()

Java

AgenticWorkflowDemo.java


package demos.determinsticWorkflow;

import com.oracle.bmc.ConfigFileReader;
import com.oracle.bmc.adk.client.AgentClient;
import com.oracle.bmc.adk.run.RunResponse;
import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider;
import com.oracle.bmc.auth.SessionTokenAuthenticationDetailsProvider;
import com.oracle.bmc.adk.agent.Agent;
import com.oracle.bmc.adk.agent.RunOptions;
import com.oracle.bmc.adk.examples.determinsticWorkflow.tools.ResearcherToolkit;
import com.oracle.bmc.adk.examples.determinsticWorkflow.tools.WriterToolkit;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AgenticWorkflowDemo {

  public static void main(String[] args) throws IOException {
    // Configuration parameters
    final String configLocation = "~/.oci/config";
    final String configProfile = "DEFAULT";
    final String researchAgentEndpointId =
        "ocid1.genaiagentendpoint...";
    final String writerAgentEndpointId =
        "ocid1.genaiagentendpoint...";

    // Initialize AgentClient
    BasicAuthenticationDetailsProvider authProvider =
            new SessionTokenAuthenticationDetailsProvider(
                    ConfigFileReader.parse(configLocation, configProfile));

    AgentClient client =
            AgentClient.builder()
                    .authProvider(authProvider)
                    .region("us-chicago-1")
                    .build();

    // Create researcher agent with its toolkit
    Agent researcher = Agent.builder()
            .client(client)
            .agentEndpointId(researchAgentEndpointId)
            .instructions(
                "You are a researcher. You research trending keywords based on the user preferences.")
            .tools(Arrays.asList(new ResearcherToolkit()))
            .name("Researcher")
            .build();

    // Create writer agent with its toolkit
    Agent writer = Agent.builder()
            .client(client)
            .agentEndpointId(writerAgentEndpointId)
            .instructions(
                "You are a writer. You write a blog post based on the trending keywords and the user preferences.")
            .tools(Arrays.asList(new WriterToolkit()))
            .name("Writer")
            .build();

    // Set up both agents
    researcher.setup();
    writer.setup();

    // Step 1: fetch user preferences (a non-agentic step)
    Map<String, Object> userPreferences = getUserPreferences();

    // Step 2: research trending keywords (agentic step)
    List<String> topics = (List<String>) userPreferences.get("topics");
    String researcherPrompt = "Research trending keywords for the following topics: " + topics;
    final Integer maxSteps = 3;
    RunOptions runOptions = RunOptions.builder().maxSteps(maxSteps).build();
    RunResponse researcherResponse = researcher.run(researcherPrompt, runOptions);
    String keywords = researcherResponse.getOutput();

    // Step 3: write a blog post (agentic step)
    List<String> style = (List<String>) userPreferences.get("style");
    String email = (String) userPreferences.get("delivery_email");
    String writerPrompt =
        "Write a 5 sentences blog post and email it to "
            + email
            + ". Use style: "
            + style
            + ". Blog post should be based on: "
            + keywords;
    RunResponse writerResponse = writer.run(writerPrompt, runOptions);

    // Step 4: print the final output
    writerResponse.prettyPrint();
  }

  // Simulate fetching user preferences (for example, from a database)
  private static Map<String, Object> getUserPreferences() {
    Map<String, Object> preferences = new HashMap<>();
    preferences.put("delivery_email", "j.jing.y.yang@oracle.com");
    preferences.put("style", Arrays.asList("casual", "humorous"));
    preferences.put("topics", Arrays.asList("ai"));
    return preferences;
  }
}

Custom Function Tools

This example uses custom toolkits for the researcher and writer agents. Here's a simplified version of what these might look like:

Python

custom_functon_tools.py

from typing import Dict, List
from oci.addons.adk import tool, Toolkit

@tool
def get_trending_keywords(topic: str) -> Dict[str, List[str]]:
    """Get trending keywords for a given topic"""
    # In a real implementation, this might call an API or database.
    if topic == "ai":
        return {"topic": topic, "keywords": ["generative AI", "multi-agent systems", "LLM agents"]}
    return {"topic": topic, "keywords": ["unknown"]}

@tool
def send_email(recipient: str, subject: str, body: str) -> str:
    """Send an email with the given subject and body to the recipient"""
    # In a real implementation, this would send an actual email.
    print(f"Sending email to {recipient}")
    print(f"Subject: {subject}")
    print(f"Body: {body}")
    return "Email sent successfully"

class ResearcherToolkit(Toolkit):
    """Toolkit for researching trending topics"""

    def __init__(self):
        super().__init__(name="ResearcherToolkit", description="Tools for researching trending topics")
        self.add_tool(get_trending_keywords)

class WriterToolkit(Toolkit):
    """Toolkit for writing content and sending emails"""

    def __init__(self):
        super().__init__(name="WriterToolkit", description="Tools for writing content and sending emails")
        self.add_tool(send_email)

Java

MultiAgentTools.java

package demos.tools;

import com.oracle.bmc.adk.tools.Param;
import com.oracle.bmc.adk.tools.Tool;
import com.oracle.bmc.adk.tools.Toolkit;

public class MultiAgentTools extends Toolkit {
  @Tool(name = "get_trending_keywords", description = "Get the trending keywords for a given topic")
  public static String getTrendingKeywords(
      @Param(description = "The topic to get trending keywords for") String topic) {
    return "{\"topic\": \"" + topic + "\", \"keywords\": [\"agent\", \"stargate\", \"openai\"]}";
  }

  @Tool(name = "send_email", description = "Send an email to a recipient")
  public static String sendEmail(
      @Param(description = "The recipient email address") String recipient,
      @Param(description = "The email subject") String subject,
      @Param(description = "The email body content") String body) {
    System.out.println(
        "Sending email to " + recipient + " with subject " + subject + " and body " + body);
    return "Sent!";
  }

}

When to use this pattern

This pattern is particularly useful when:

  • You need predictable, repeatable execution.
  • Parts of your workflow depend on existing systems or databases.
  • You need to control exactly when and how agents are invoked.
  • Business logic demands specific sequences that shouldn't be left to agent decision-making.