34
34
import org .springframework .ai .embedding .EmbeddingResponse ;
35
35
import org .springframework .util .Assert ;
36
36
37
+ import io .micrometer .observation .ObservationRegistry ;
38
+ import io .micrometer .observation .Observation ;
39
+
37
40
/**
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
40
45
* base64) inputs.
41
46
*
42
47
* Note: Titan Embedding does not support batch embedding.
@@ -51,17 +56,24 @@ public class BedrockTitanEmbeddingModel extends AbstractEmbeddingModel {
51
56
52
57
private final TitanEmbeddingBedrockApi embeddingApi ;
53
58
59
+ private final ObservationRegistry observationRegistry ;
60
+
54
61
/**
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).
56
64
*/
57
65
private InputType inputType = InputType .TEXT ;
58
66
59
- public BedrockTitanEmbeddingModel (TitanEmbeddingBedrockApi titanEmbeddingBedrockApi ) {
67
+ public BedrockTitanEmbeddingModel (TitanEmbeddingBedrockApi titanEmbeddingBedrockApi ,
68
+ ObservationRegistry observationRegistry ) {
60
69
this .embeddingApi = titanEmbeddingBedrockApi ;
70
+ this .observationRegistry = observationRegistry ;
61
71
}
62
72
63
73
/**
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
+ *
65
77
* @param inputType the input type to use.
66
78
*/
67
79
public BedrockTitanEmbeddingModel withInputType (InputType inputType ) {
@@ -78,17 +90,40 @@ public float[] embed(Document document) {
78
90
public EmbeddingResponse call (EmbeddingRequest request ) {
79
91
Assert .notEmpty (request .getInstructions (), "At least one text is required!" );
80
92
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." );
83
94
}
84
95
85
96
List <Embedding > embeddings = new ArrayList <>();
86
97
var indexCounter = new AtomicInteger (0 );
98
+
87
99
for (String inputContent : request .getInstructions ()) {
88
100
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
+ }
91
125
}
126
+
92
127
return new EmbeddingResponse (embeddings );
93
128
}
94
129
@@ -117,6 +152,13 @@ public int dimensions() {
117
152
118
153
}
119
154
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
+
120
162
public enum InputType {
121
163
122
164
TEXT , IMAGE
0 commit comments