3434import org .springframework .ai .embedding .EmbeddingResponse ;
3535import org .springframework .util .Assert ;
3636
37+ import io .micrometer .observation .ObservationRegistry ;
38+ import io .micrometer .observation .Observation ;
39+
3740/**
38- * {@link org.springframework.ai.embedding.EmbeddingModel} implementation that uses the
39- * Bedrock Titan Embedding API. Titan Embedding supports text and image (encoded in
41+ * {@link org.springframework.ai.embedding.EmbeddingModel} implementation that
42+ * uses the
43+ * Bedrock Titan Embedding API. Titan Embedding supports text and image (encoded
44+ * in
4045 * base64) inputs.
4146 *
4247 * Note: Titan Embedding does not support batch embedding.
@@ -51,17 +56,24 @@ public class BedrockTitanEmbeddingModel extends AbstractEmbeddingModel {
5156
5257 private final TitanEmbeddingBedrockApi embeddingApi ;
5358
59+ private final ObservationRegistry observationRegistry ;
60+
5461 /**
55- * Titan Embedding API input types. Could be either text or image (encoded in base64).
62+ * Titan Embedding API input types. Could be either text or image (encoded in
63+ * base64).
5664 */
5765 private InputType inputType = InputType .TEXT ;
5866
59- public BedrockTitanEmbeddingModel (TitanEmbeddingBedrockApi titanEmbeddingBedrockApi ) {
67+ public BedrockTitanEmbeddingModel (TitanEmbeddingBedrockApi titanEmbeddingBedrockApi ,
68+ ObservationRegistry observationRegistry ) {
6069 this .embeddingApi = titanEmbeddingBedrockApi ;
70+ this .observationRegistry = observationRegistry ;
6171 }
6272
6373 /**
64- * Titan Embedding API input types. Could be either text or image (encoded in base64).
74+ * Titan Embedding API input types. Could be either text or image (encoded in
75+ * base64).
76+ *
6577 * @param inputType the input type to use.
6678 */
6779 public BedrockTitanEmbeddingModel withInputType (InputType inputType ) {
@@ -78,17 +90,40 @@ public float[] embed(Document document) {
7890 public EmbeddingResponse call (EmbeddingRequest request ) {
7991 Assert .notEmpty (request .getInstructions (), "At least one text is required!" );
8092 if (request .getInstructions ().size () != 1 ) {
81- logger .warn (
82- "Titan Embedding does not support batch embedding. Will make multiple API calls to embed(Document)" );
93+ logger .warn ("Titan Embedding does not support batch embedding. Multiple API calls will be made." );
8394 }
8495
8596 List <Embedding > embeddings = new ArrayList <>();
8697 var indexCounter = new AtomicInteger (0 );
98+
8799 for (String inputContent : request .getInstructions ()) {
88100 var apiRequest = createTitanEmbeddingRequest (inputContent , request .getOptions ());
89- TitanEmbeddingResponse response = this .embeddingApi .embedding (apiRequest );
90- embeddings .add (new Embedding (response .embedding (), indexCounter .getAndIncrement ()));
101+
102+ try {
103+ TitanEmbeddingResponse response = Observation
104+ .createNotStarted ("bedrock.embedding" , this .observationRegistry )
105+ .lowCardinalityKeyValue ("model" , "titan" )
106+ .lowCardinalityKeyValue ("input_type" , this .inputType .name ().toLowerCase ())
107+ .highCardinalityKeyValue ("input_length" , String .valueOf (inputContent .length ()))
108+ .observe (() -> {
109+ TitanEmbeddingResponse r = this .embeddingApi .embedding (apiRequest );
110+ Assert .notNull (r , "Embedding API returned null response" );
111+ return r ;
112+ });
113+
114+ if (response .embedding () == null || response .embedding ().length == 0 ) {
115+ logger .warn ("Empty embedding vector returned for input at index {}. Skipping." , indexCounter .get ());
116+ continue ;
117+ }
118+
119+ embeddings .add (new Embedding (response .embedding (), indexCounter .getAndIncrement ()));
120+ } catch (Exception ex ) {
121+ logger .error ("Titan API embedding failed for input at index {}: {}" , indexCounter .get (),
122+ summarizeInput (inputContent ), ex );
123+ throw ex ; // Optional: Continue instead of throwing if you want partial success
124+ }
91125 }
126+
92127 return new EmbeddingResponse (embeddings );
93128 }
94129
@@ -117,6 +152,13 @@ public int dimensions() {
117152
118153 }
119154
155+ private String summarizeInput (String input ) {
156+ if (this .inputType == InputType .IMAGE ) {
157+ return "[image content omitted, length=" + input .length () + "]" ;
158+ }
159+ return input .length () > 100 ? input .substring (0 , 100 ) + "..." : input ;
160+ }
161+
120162 public enum InputType {
121163
122164 TEXT , IMAGE
0 commit comments