Inference clients depend on stable field names as much as they depend on model weights. A TensorFlow SavedModel can expose several callable signatures so a standard score request and a richer classification request share one deployable artifact without leaking graph tensor names into client code.
TensorFlow's tf.keras.export.ExportArchive registers each endpoint as a concrete function with a declared TensorSpec. The TensorSpec name becomes the request key, while dictionary keys returned by the endpoint become response keys.
The first endpoint registered is also exported as serving_default unless that name is added manually. Treat endpoint and field names as an API contract because changing them can break TensorFlow Serving clients even when the model computation is unchanged.
import os from pathlib import Path os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" import tensorflow as tf tf.get_logger().setLevel("ERROR") export_dir = Path("priority_serving") features = tf.keras.Input(shape=(4,), name="features") scores = tf.keras.layers.Dense( 1, activation="sigmoid", kernel_initializer=tf.keras.initializers.Constant([[1.2], [0.8], [-0.6], [1.0]]), bias_initializer=tf.keras.initializers.Constant([-0.4]), name="scores", )(features) model = tf.keras.Model(features, scores, name="support_priority")
The fixed initializers keep the sample output repeatable; production code supplies the trained Keras model at this point.
@tf.function( input_signature=[tf.TensorSpec((None, 4), tf.float32, name="features")] ) def score(features): return {"scores": model(features, training=False)}
@tf.function( input_signature=[tf.TensorSpec((None, 4), tf.float32, name="features")] ) def classify(features): scores = model(features, training=False) labels = tf.cast(scores >= 0.5, tf.int32) return {"scores": scores, "labels": labels}
archive = tf.keras.export.ExportArchive() archive.track(model) archive.add_endpoint("score", score) archive.add_endpoint("classify", classify) archive.write_out(export_dir, verbose=False)
The first registered endpoint, score, also receives the serving_default alias required by the default TensorFlow Serving prediction path.
$ python3 export_signatures.py
$ saved_model_cli show --dir priority_serving --tag_set serve The given SavedModel MetaGraphDef contains SignatureDefs with the following keys: SignatureDef key: "__saved_model_init_op" SignatureDef key: "classify" SignatureDef key: "score" SignatureDef key: "serving_default"
$ saved_model_cli show \
--dir priority_serving \
--tag_set serve \
--signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
inputs['features'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 4)
name: serving_default_features:0
The given SavedModel SignatureDef contains the following output(s):
outputs['scores'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall_2:0
Method name is: tensorflow/serving/predict
Serving clients continue to depend on the features and scores keys after integration.
$ saved_model_cli run \ --dir priority_serving \ --tag_set serve \ --signature_def classify \ --input_exprs 'features=[[0.2,0.9,0.1,0.8],[0.1,0.1,0.9,0.1]]' ##### snipped ##### Result for output key labels: [[1] [0]] Result for output key scores: [[0.78583497] [0.34524652]]