Skip to content

Commit 1ee9094

Browse files
ThomasVitaletzolov
authored andcommitted
Improve chat client and advisor observations
* Make ChatClient and Advisor observation logic null-safe * Simplify naming for Advisor observations * Include high-cardinality attributes only if a value is present * Fix condition to include system test to chat client observations * Add Advisor order information to context * Streamline usage of enums and utils to reduce hard-coded/duplications * Fix pending Chroma integration test Signed-off-by: Thomas Vitale <[email protected]>
1 parent 8ea5632 commit 1ee9094

21 files changed

+308
-221
lines changed

spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* @author Arjen Poutsma
7878
* @author Soby Chacko
7979
* @author Dariusz Jedrzejczyk
80+
* @author Thomas Vitale
8081
* @since 1.0.0
8182
*/
8283
public class DefaultChatClient implements ChatClient {
@@ -360,8 +361,11 @@ private ChatResponse doGetChatResponse() {
360361
private ChatResponse doGetObservableChatResponse(DefaultChatClientRequestSpec inputRequest,
361362
String formatParam) {
362363

363-
ChatClientObservationContext observationContext = new ChatClientObservationContext(inputRequest,
364-
formatParam, false);
364+
ChatClientObservationContext observationContext = ChatClientObservationContext.builder()
365+
.withRequest(inputRequest)
366+
.withFormat(formatParam)
367+
.withStream(false)
368+
.build();
365369

366370
var observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(
367371
inputRequest.getCustomObservationConvention(), DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION,
@@ -407,8 +411,10 @@ public DefaultStreamResponseSpec(DefaultChatClientRequestSpec request) {
407411
private Flux<ChatResponse> doGetObservableFluxChatResponse(DefaultChatClientRequestSpec inputRequest) {
408412
return Flux.deferContextual(contextView -> {
409413

410-
ChatClientObservationContext observationContext = new ChatClientObservationContext(inputRequest, "",
411-
true);
414+
ChatClientObservationContext observationContext = ChatClientObservationContext.builder()
415+
.withRequest(inputRequest)
416+
.withStream(true)
417+
.build();
412418

413419
Observation observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(
414420
inputRequest.getCustomObservationConvention(), DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION,

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest) {
8585
.withAdvisorType(AdvisorObservationContext.Type.AROUND)
8686
.withAdvisedRequest(advisedRequest)
8787
.withAdvisorRequestContext(advisedRequest.adviseContext())
88+
.withOrder(advisor.getOrder())
8889
.build();
8990

9091
return AdvisorObservationDocumentation.AI_ADVISOR
@@ -106,6 +107,7 @@ public Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest) {
106107
.withAdvisorType(AdvisorObservationContext.Type.AROUND)
107108
.withAdvisedRequest(advisedRequest)
108109
.withAdvisorRequestContext(advisedRequest.adviseContext())
110+
.withOrder(advisor.getOrder())
109111
.build();
110112

111113
var observation = AdvisorObservationDocumentation.AI_ADVISOR.observation(null,

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContext.java

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020
import org.springframework.ai.chat.client.ChatClient;
2121
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
2222
import org.springframework.ai.chat.prompt.Prompt;
23+
import org.springframework.lang.Nullable;
2324
import org.springframework.util.Assert;
2425

2526
import io.micrometer.observation.Observation;
2627

2728
/**
29+
* Context used to store metadata for chat client advisors.
30+
*
2831
* @author Christian Tzolov
32+
* @author Thomas Vitale
2933
* @since 1.0.0
3034
*/
31-
3235
public class AdvisorObservationContext extends Observation.Context {
3336

3437
public enum Type {
@@ -37,35 +40,48 @@ public enum Type {
3740

3841
}
3942

40-
private String advisorName;
43+
private final String advisorName;
4144

42-
private Type advisorType;
45+
private final Type advisorType;
4346

4447
/**
4548
* The {@link AdvisedRequest} data to be advised. Represents the row
4649
* {@link ChatClient.ChatClientRequestSpec} data before sealed into a {@link Prompt}.
4750
*/
51+
@Nullable
4852
private AdvisedRequest advisorRequest;
4953

5054
/**
5155
* The shared data between the advisors in the chain. It is shared between all request
5256
* and response advising points of all advisors in the chain.
5357
*/
58+
@Nullable
5459
private Map<String, Object> advisorRequestContext;
5560

5661
/**
5762
* the shared data between the advisors in the chain. It is shared between all request
5863
* and response advising points of all advisors in the chain.
5964
*/
65+
@Nullable
6066
private Map<String, Object> advisorResponseContext;
6167

6268
/**
6369
* The order of the advisor in the advisor chain.
6470
*/
65-
private int order;
71+
private final int order;
72+
73+
public AdvisorObservationContext(String advisorName, Type advisorType, @Nullable AdvisedRequest advisorRequest,
74+
@Nullable Map<String, Object> advisorRequestContext, @Nullable Map<String, Object> advisorResponseContext,
75+
int order) {
76+
Assert.hasText(advisorName, "advisorName must not be null or empty");
77+
Assert.notNull(advisorType, "advisorType must not be null");
6678

67-
public void setAdvisorName(String advisorName) {
6879
this.advisorName = advisorName;
80+
this.advisorType = advisorType;
81+
this.advisorRequest = advisorRequest;
82+
this.advisorRequestContext = advisorRequestContext;
83+
this.advisorResponseContext = advisorResponseContext;
84+
this.order = order;
6985
}
7086

7187
public String getAdvisorName() {
@@ -76,84 +92,88 @@ public Type getAdvisorType() {
7692
return this.advisorType;
7793
}
7894

79-
public void setAdvisorType(Type type) {
80-
this.advisorType = type;
81-
}
82-
95+
@Nullable
8396
public AdvisedRequest getAdvisedRequest() {
8497
return this.advisorRequest;
8598
}
8699

87-
public void setAdvisedRequest(AdvisedRequest advisedRequest) {
100+
public void setAdvisedRequest(@Nullable AdvisedRequest advisedRequest) {
88101
this.advisorRequest = advisedRequest;
89102
}
90103

104+
@Nullable
91105
public Map<String, Object> getAdvisorRequestContext() {
92106
return this.advisorRequestContext;
93107
}
94108

95-
public void setAdvisorRequestContext(Map<String, Object> advisorRequestContext) {
109+
public void setAdvisorRequestContext(@Nullable Map<String, Object> advisorRequestContext) {
96110
this.advisorRequestContext = advisorRequestContext;
97111
}
98112

113+
@Nullable
99114
public Map<String, Object> getAdvisorResponseContext() {
100115
return this.advisorResponseContext;
101116
}
102117

103-
public void setAdvisorResponseContext(Map<String, Object> advisorResponseContext) {
118+
public void setAdvisorResponseContext(@Nullable Map<String, Object> advisorResponseContext) {
104119
this.advisorResponseContext = advisorResponseContext;
105120
}
106121

107122
public int getOrder() {
108123
return this.order;
109124
}
110125

111-
public void setOrder(int order) {
112-
this.order = order;
113-
}
114-
115126
public static Builder builder() {
116127
return new Builder();
117128
}
118129

119130
public static class Builder {
120131

121-
private final AdvisorObservationContext context = new AdvisorObservationContext();
132+
private String advisorName;
133+
134+
private Type advisorType;
135+
136+
private AdvisedRequest advisorRequest;
137+
138+
private Map<String, Object> advisorRequestContext;
139+
140+
private Map<String, Object> advisorResponseContext;
141+
142+
private int order = 0;
122143

123144
public Builder withAdvisorName(String advisorName) {
124-
this.context.setAdvisorName(advisorName);
145+
this.advisorName = advisorName;
125146
return this;
126147
}
127148

128149
public Builder withAdvisorType(Type advisorType) {
129-
this.context.setAdvisorType(advisorType);
150+
this.advisorType = advisorType;
130151
return this;
131152
}
132153

133154
public Builder withAdvisedRequest(AdvisedRequest advisedRequest) {
134-
this.context.setAdvisedRequest(advisedRequest);
155+
this.advisorRequest = advisedRequest;
135156
return this;
136157
}
137158

138159
public Builder withAdvisorRequestContext(Map<String, Object> advisorRequestContext) {
139-
this.context.setAdvisorRequestContext(advisorRequestContext);
160+
this.advisorRequestContext = advisorRequestContext;
140161
return this;
141162
}
142163

143164
public Builder withAdvisorResponseContext(Map<String, Object> advisorResponseContext) {
144-
this.context.setAdvisorResponseContext(advisorResponseContext);
165+
this.advisorResponseContext = advisorResponseContext;
145166
return this;
146167
}
147168

148169
public Builder withOrder(int order) {
149-
this.context.setOrder(order);
170+
this.order = order;
150171
return this;
151172
}
152173

153174
public AdvisorObservationContext build() {
154-
Assert.hasText(this.context.advisorName, "The advisorName must not be empty!");
155-
Assert.notNull(this.context.advisorType, "The advisorType must not be null!");
156-
return this.context;
175+
return new AdvisorObservationContext(advisorName, advisorType, advisorRequest, advisorRequestContext,
176+
advisorResponseContext, order);
157177
}
158178

159179
}

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationConvention.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import io.micrometer.observation.ObservationConvention;
2020

2121
/**
22+
* Interface for an {@link ObservationConvention} for chat client advisors.
23+
*
2224
* @author Christian Tzolov
2325
* @since 1.0.0
2426
*/

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationDocumentation.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
public enum AdvisorObservationDocumentation implements ObservationDocumentation {
2828

2929
/**
30-
* AI Chat Client observations
30+
* AI Advisor observations
3131
*/
3232
AI_ADVISOR {
3333
@Override
@@ -65,7 +65,7 @@ public String asString() {
6565
ADVISOR_TYPE {
6666
@Override
6767
public String asString() {
68-
return "spring.ai.chat.client.advisor.type";
68+
return "spring.ai.advisor.type";
6969
}
7070
}
7171

@@ -74,12 +74,12 @@ public String asString() {
7474
public enum HighCardinalityKeyNames implements KeyName {
7575

7676
/**
77-
* Chat Model name.
77+
* Advisor name.
7878
*/
7979
ADVISOR_NAME {
8080
@Override
8181
public String asString() {
82-
return "spring.ai.chat.client.advisor.name";
82+
return "spring.ai.advisor.name";
8383
}
8484
},
8585
/**
@@ -88,7 +88,7 @@ public String asString() {
8888
ADVISOR_ORDER {
8989
@Override
9090
public String asString() {
91-
return "spring.ai.chat.client.advisor.order";
91+
return "spring.ai.advisor.order";
9292
}
9393
}
9494

spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConvention.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationDocumentation.HighCardinalityKeyNames;
1919
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationDocumentation.LowCardinalityKeyNames;
20+
import org.springframework.ai.observation.conventions.SpringAiKind;
2021
import org.springframework.ai.util.ParsingUtils;
2122
import org.springframework.lang.Nullable;
2223

@@ -27,18 +28,9 @@
2728
* @author Christian Tzolov
2829
* @since 1.0.0
2930
*/
30-
3131
public class DefaultAdvisorObservationConvention implements AdvisorObservationConvention {
3232

33-
public static final String DEFAULT_NAME = "spring.ai.chat.client.advisor";
34-
35-
private static final String CHAT_CLIENT_ADVISOR_SPRING_AI_KIND = "chat_client_advisor";
36-
37-
private static final KeyValue ADVISOR_TYPE_NONE = KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE,
38-
KeyValue.NONE_VALUE);
39-
40-
private static final KeyValue ADVISOR_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME,
41-
KeyValue.NONE_VALUE);
33+
public static final String DEFAULT_NAME = "spring.ai.advisor";
4234

4335
private final String name;
4436

@@ -73,15 +65,12 @@ public KeyValues getLowCardinalityKeyValues(AdvisorObservationContext context) {
7365
}
7466

7567
protected KeyValue advisorType(AdvisorObservationContext context) {
76-
if (context.getAdvisorType() != null) {
77-
return KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE, context.getAdvisorType().name());
78-
}
79-
return ADVISOR_TYPE_NONE;
68+
return KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE, context.getAdvisorType().name());
8069
}
8170

8271
protected KeyValue springAiKind() {
8372
return KeyValue.of(AdvisorObservationDocumentation.LowCardinalityKeyNames.SPRING_AI_KIND,
84-
CHAT_CLIENT_ADVISOR_SPRING_AI_KIND);
73+
SpringAiKind.ADVISOR.value());
8574
}
8675

8776
// ------------------------
@@ -94,14 +83,11 @@ public KeyValues getHighCardinalityKeyValues(AdvisorObservationContext context)
9483
}
9584

9685
protected KeyValue advisorName(AdvisorObservationContext context) {
97-
if (context.getAdvisorType() != null) {
98-
return KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME, context.getAdvisorName());
99-
}
100-
return ADVISOR_NAME_NONE;
86+
return KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME, context.getAdvisorName());
10187
}
10288

10389
protected KeyValue advisorOrder(AdvisorObservationContext context) {
10490
return KeyValue.of(HighCardinalityKeyNames.ADVISOR_ORDER, "" + context.getOrder());
10591
}
10692

107-
}
93+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@NonNullApi
18+
@NonNullFields
19+
package org.springframework.ai.chat.client.advisor.observation;
20+
21+
import org.springframework.lang.NonNullApi;
22+
import org.springframework.lang.NonNullFields;

0 commit comments

Comments
 (0)