Configure Workflows through Jupyter
In this guide, you will create a simple workflow using the C3 AI Workflows Python API. You will build a straightforward two-node workflow without complex logic or branching.
Prerequisites
Before you begin, ensure the following:
- You can access Jupyter Notebook and the Application C3 AI Console.
Open Jupyter Notebook
Open Jupyter Notebook from the C3 Generative AI Application card:
- Navigate to your application in C3 AI Studio.
- Select Jupyter Notebook.
- Set the
py-workflows_312runtime as the notebook kernel.
Overview of key concepts
Workflows have several components:
- Nodes: Individual steps that do specific tasks
- Workflow State: Shared memory that holds data between nodes
- Edges: Connections that control which node runs next
- Templates: Blueprints for creating nodes
Build a workflow with two nodes
This tutorial shows you how to create and execute a workflow with two nodes to get user input (a name) and return a greeting.
- Setup and create a workflow
- Register a node template
- Create the first node to collect the user's name
- Create the second node to generate a greeting
- Connect the nodes together
- Add the nodes to the workflow state and specify mappings
- Validate your greeting workflow for completeness
- Update the graph instance and re-publish the workflow
- Execute the workflow
- Handle interrupt nodes and provide user input
- Print the final results
- Visualize the workflow structure in Mermaid
1. Setup and create a workflow
Start by importing required packages and creating a basic workflow:
# Basic imports
from workflows import Graph
from workflows.components import (
FieldTypeSchema,
Node,
ConditionalEdges,
)
# Create a simple workflow
workflow = c3.Genai.Agent.Resource.Workflow(
id="Simple Greeting Workflow",
name="Simple Greeting Workflow",
projects=[c3.Genai.Project.forId("default")]
)
# Create the native graph
graph = Graph()
# Attach the native graph to the persisted workflow entity
workflow = workflow.updateNative(graph)
graph = workflow.toNative()2. Register a node template
There are several available node templates, including:
- **get_user_input_node_template**: Displays a message and pauses the workflow until the user enters text. Produces one output 'user_input' as a string.
- **code_agent_node_template**: Executes a Code Agent call. that can run Python code and search the internet. Both inputs and outputs are configurable.
- **c3_type_input_node_template**: Accepts a C3 Type from the whitelisted data model and makes it available to the application for processing.Register two templates to have the user input their name and to create a greeting:
# Get the templates we need
string_input_template = c3.Genai.Workflow.NodeTemplate.forName("string_input_node_template").toNative()
llm_template = c3.Genai.Workflow.NodeTemplate.forName("llm_node_template").toNative()
# Add the templates to the graph
graph.update_node_templates([string_input_template, llm_template], force_update=True)
print("✅ Templates registered")3. Create the first node to collect the user's name
# Node 1: Get the user's name
name_input_node = graph.register_node_from_template(
template_name="string_input_node_template",
node_name="GetUserName",
description="Ask the user for their name",
output_spec={"user_name": str},
llm_config_values={
"input_description": "What's your name?",
"instructions": "Just return the user's name as a simple string. No extra formatting."
}
)
print("✅ Created GetUserName node")4. Create the second node to generate a greeting
# Node 2: Generate a greeting using the name
greeting_node = graph.register_node_from_template(
template_name="llm_node_template",
node_name="GenerateGreeting",
description="Create a friendly greeting message",
input_spec={"user_name": str},
output_spec={"greeting_message": str},
llm_config_values={
"instructions": "Create a friendly, warm greeting message using the user's name. Keep it simple and cheerful.",
"output_schema": {
"type": "object",
"properties": {
"greeting_message": {
"type": "string",
"description": "A friendly greeting message that includes the user's name"
}
},
"required": ["greeting_message"],
"additionalProperties": False
}
}
)
print("✅ Created GenerateGreeting node")5. Connect the nodes together
# Create a simple edge connecting the name input to the greeting generator
graph.add_edge(
"GetUserName",
"GenerateGreeting"
)
print("✅ Connected nodes: GetUserName → GenerateGreeting")6. Add the nodes to the workflow state and specify mappings
# Add all fields to the workflow state
for node_name, node in graph.nodes.items():
for key, val in node.output_spec.fields.items():
graph.add_field_to_state(key, val)
# Add input and output mappings for all nodes
for node_name, node in graph.nodes.items():
for key, val in node.input_spec.fields.items():
graph.add_input_mapping(state_field=key, target_node=node_name, target_input=key, force_update=True)
for node_name, node in graph.nodes.items():
for key, val in node.output_spec.fields.items():
graph.add_output_mapping(source_node=node_name, source_output=key, state_field=key)
print("✅ Added input/output mappings")7. Validate your greeting workflow for completeness
_ = graph.diagnose_graph_completeness(display_results=True)8. Update the graph instance and re-publish the workflow
workflow = workflow.updateNative(graph)
workflow = workflow.get().publish()9. Execute the workflow
# Create execution. If not provided, execution is auto-generated.
workflow_execution = c3.Genai.WorkflowExecution(
id="greeting_execution",
name="Greeting Execution",
workflow=workflow
).upsert()
# Execute the workflow
results = workflow.executeWorkflow(
workflowExecution=workflow_execution,
input={}, # Empty because none of the state variables are initialized to anything (all None)
config=None,
kwargs={}
)
print("✅ Workflow started successfully!")10. Handle interrupt nodes and provide user input
Since the username node interrupts the user for a response, it is an interrupt node. You can check for its status with the following code. Update the payload to try different names.
# Check for pending interrupts
pending_interrupts = workflow.getPendingInterrupts(workflow_execution.id)
if pending_interrupts:
interrupt = pending_interrupts[0]
# Provide the user's name
resume_response = c3.Genai.Workflow.ResumeResponse(
interruptId=interrupt.interruptId,
payload="Bob" # The user's name
)
# Resume execution
resumed_results = list(workflow.resumeWorkflow(workflow_execution, [resume_response]))
print("✅ Workflow resumed and completed!")11. Print the final results
# Get the final results
final_state = workflow.getNativeExecutionStateValues(workflow_execution)
print(f"User Name: {final_state.get('user_name', 'Not found')}")
print(f"Greeting: {final_state.get('greeting_message', 'Not found')}")12. Visualize the workflow structure in Mermaid
# Check that the workflow is properly connected
_ = graph.diagnose_graph_completeness(display_results=True)
# Visualize the simple workflow
mermaid_spec = graph.visualize(direction="LR", sub_direction="TB", show_io=True)
print("Workflow visualization created!")Adding More Complexity
Once you are comfortable with this simple workflow, you can:
- Add more nodes: Create additional processing steps
- Add conditional logic: Route based on user input
- Add error handling: Handle unexpected inputs gracefully
- Save/load state: Persist workflow progress