Skip to content

Commit bd5142a

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents 271a054 + 5e86583 commit bd5142a

File tree

11 files changed

+1107
-11
lines changed

11 files changed

+1107
-11
lines changed

document-readers/tika-reader/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
</scm>
3838

3939
<properties>
40-
<tika.version>3.0.0-BETA2</tika.version>
40+
<tika.version>3.0.0</tika.version>
4141
</properties>
4242

4343
<dependencies>

document-readers/tika-reader/src/main/java/org/springframework/ai/reader/tika/TikaDocumentReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
/**
3838
* A document reader that leverages Apache Tika to extract text from a variety of document
3939
* formats, such as PDF, DOC/DOCX, PPT/PPTX, and HTML. For a comprehensive list of
40-
* supported formats, refer to: https://tika.apache.org/3.0.0-BETA2/formats.html.
40+
* supported formats, refer to: https://tika.apache.org/3.0.0/formats.html.
4141
*
4242
* This reader directly provides the extracted text without any additional formatting. All
4343
* extracted texts are encapsulated within a {@link Document} instance.

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ void streamingWithTokenUsage() {
140140
assertThat(streamingTokenUsage.getTotalTokens()).isGreaterThan(0);
141141

142142
assertThat(streamingTokenUsage.getPromptTokens()).isEqualTo(referenceTokenUsage.getPromptTokens());
143-
assertThat(streamingTokenUsage.getGenerationTokens()).isEqualTo(referenceTokenUsage.getGenerationTokens());
144-
assertThat(streamingTokenUsage.getTotalTokens()).isEqualTo(referenceTokenUsage.getTotalTokens());
143+
// assertThat(streamingTokenUsage.getGenerationTokens()).isEqualTo(referenceTokenUsage.getGenerationTokens());
144+
// assertThat(streamingTokenUsage.getTotalTokens()).isEqualTo(referenceTokenUsage.getTotalTokens());
145145

146146
}
147147

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright 2023-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+
package org.springframework.ai.anthropic.client;
18+
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import org.springframework.ai.anthropic.AnthropicTestConfiguration;
29+
import org.springframework.ai.chat.client.ChatClient;
30+
import org.springframework.ai.chat.model.ChatModel;
31+
import org.springframework.ai.chat.model.ToolContext;
32+
import org.springframework.ai.model.function.MethodFunctionCallback;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.boot.test.context.SpringBootTest;
35+
import org.springframework.test.context.ActiveProfiles;
36+
import org.springframework.util.ReflectionUtils;
37+
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
import static org.junit.Assert.assertThrows;
40+
41+
@SpringBootTest(classes = AnthropicTestConfiguration.class, properties = "spring.ai.retry.on-http-codes=429")
42+
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
43+
@ActiveProfiles("logging-test")
44+
class AnthropicChatClientMethodFunctionCallbackIT {
45+
46+
private static final Logger logger = LoggerFactory.getLogger(AnthropicChatClientMethodFunctionCallbackIT.class);
47+
48+
public static Map<String, Object> arguments = new ConcurrentHashMap<>();
49+
50+
@Autowired
51+
ChatModel chatModel;
52+
53+
record MyRecord(String foo, String bar) {
54+
}
55+
56+
public enum Unit {
57+
58+
CELSIUS, FAHRENHEIT
59+
60+
}
61+
62+
public static class TestFunctionClass {
63+
64+
public static void argumentLessReturnVoid() {
65+
arguments.put("method called", "argumentLessReturnVoid");
66+
}
67+
68+
public static String getWeatherStatic(String city, Unit unit) {
69+
70+
logger.info("City: " + city + " Unit: " + unit);
71+
72+
arguments.put("city", city);
73+
arguments.put("unit", unit);
74+
75+
double temperature = 0;
76+
if (city.contains("Paris")) {
77+
temperature = 15;
78+
}
79+
else if (city.contains("Tokyo")) {
80+
temperature = 10;
81+
}
82+
else if (city.contains("San Francisco")) {
83+
temperature = 30;
84+
}
85+
86+
return "temperature: " + temperature + " unit: " + unit;
87+
}
88+
89+
public String getWeatherNonStatic(String city, Unit unit) {
90+
return getWeatherStatic(city, unit);
91+
}
92+
93+
public String getWeatherWithContext(String city, Unit unit, ToolContext context) {
94+
arguments.put("tool", context.getContext().get("tool"));
95+
return getWeatherStatic(city, unit);
96+
}
97+
98+
public void turnLight(String roomName, boolean on) {
99+
arguments.put("roomName", roomName);
100+
arguments.put("on", on);
101+
logger.info("Turn light in room: {} to: {}", roomName, on);
102+
}
103+
104+
public void turnLivingRoomLightOn() {
105+
arguments.put("turnLivingRoomLightOn", true);
106+
}
107+
108+
}
109+
110+
@BeforeEach
111+
void beforeEach() {
112+
arguments.clear();
113+
}
114+
115+
@Test
116+
void methodGetWeatherStatic() {
117+
118+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherStatic", String.class, Unit.class);
119+
// @formatter:off
120+
String response = ChatClient.create(this.chatModel).prompt()
121+
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
122+
.functions(MethodFunctionCallback.builder()
123+
.method(method)
124+
.description("Get the weather in location")
125+
.build())
126+
.call()
127+
.content();
128+
// @formatter:on
129+
130+
logger.info("Response: {}", response);
131+
132+
assertThat(response).contains("30", "10", "15");
133+
}
134+
135+
@Test
136+
void methodTurnLightNoResponse() {
137+
138+
TestFunctionClass targetObject = new TestFunctionClass();
139+
140+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLight", String.class, boolean.class);
141+
142+
// @formatter:off
143+
String response = ChatClient.create(this.chatModel).prompt()
144+
.user("Turn light on in the living room.")
145+
.functions(MethodFunctionCallback.builder()
146+
.functionObject(targetObject)
147+
.method(method)
148+
.description("Can turn lights on or off by room name")
149+
.build())
150+
.call()
151+
.content();
152+
// @formatter:on
153+
154+
logger.info("Response: {}", response);
155+
156+
assertThat(arguments).containsEntry("roomName", "living room");
157+
assertThat(arguments).containsEntry("on", true);
158+
}
159+
160+
@Test
161+
void methodGetWeatherNonStatic() {
162+
163+
TestFunctionClass targetObject = new TestFunctionClass();
164+
165+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
166+
Unit.class);
167+
168+
// @formatter:off
169+
String response = ChatClient.create(this.chatModel).prompt()
170+
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
171+
.functions(MethodFunctionCallback.builder()
172+
.functionObject(targetObject)
173+
.method(method)
174+
.description("Get the weather in location")
175+
.build())
176+
.call()
177+
.content();
178+
// @formatter:on
179+
180+
logger.info("Response: {}", response);
181+
182+
assertThat(response).contains("30", "10", "15");
183+
}
184+
185+
@Test
186+
void methodGetWeatherToolContext() {
187+
188+
TestFunctionClass targetObject = new TestFunctionClass();
189+
190+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
191+
Unit.class, ToolContext.class);
192+
193+
// @formatter:off
194+
String response = ChatClient.create(this.chatModel).prompt()
195+
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
196+
.functions(MethodFunctionCallback.builder()
197+
.functionObject(targetObject)
198+
.method(method)
199+
.description("Get the weather in location")
200+
.build())
201+
.toolContext(Map.of("tool", "value"))
202+
.call()
203+
.content();
204+
// @formatter:on
205+
206+
logger.info("Response: {}", response);
207+
208+
assertThat(response).contains("30", "10", "15");
209+
assertThat(arguments).containsEntry("tool", "value");
210+
}
211+
212+
@Test
213+
void methodGetWeatherToolContextButNonContextMethod() {
214+
215+
TestFunctionClass targetObject = new TestFunctionClass();
216+
217+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
218+
Unit.class);
219+
220+
// @formatter:off
221+
assertThrows("Configured method does not accept ToolContext as input parameter!",IllegalArgumentException.class, () -> {
222+
ChatClient.create(this.chatModel).prompt()
223+
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
224+
.functions(MethodFunctionCallback.builder()
225+
.functionObject(targetObject)
226+
.method(method)
227+
.description("Get the weather in location")
228+
.build())
229+
.toolContext(Map.of("tool", "value"))
230+
.call()
231+
.content();
232+
});
233+
// @formatter:on
234+
}
235+
236+
@Test
237+
void methodNoParameters() {
238+
239+
TestFunctionClass targetObject = new TestFunctionClass();
240+
241+
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLivingRoomLightOn");
242+
243+
// @formatter:off
244+
String response = ChatClient.create(this.chatModel).prompt()
245+
.user("Turn light on in the living room.")
246+
.functions(MethodFunctionCallback.builder()
247+
.functionObject(targetObject)
248+
.method(method)
249+
.description("Can turn lights on in the Living Room")
250+
.build())
251+
.call()
252+
.content();
253+
// @formatter:on
254+
255+
logger.info("Response: {}", response);
256+
257+
assertThat(arguments).containsEntry("turnLivingRoomLightOn", true);
258+
}
259+
260+
}

0 commit comments

Comments
 (0)