Skip to content

Commit 77b86d4

Browse files
authored
Merge pull request #716 from ashakirin/fixes/conversation-id-and-streaming
Fixed conversation id, streaming, added testcontainers
2 parents ae9726f + f2f7e24 commit 77b86d4

13 files changed

Lines changed: 183 additions & 48 deletions

samples/spring-ai-agent/chat-requests.http

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44
POST localhost:8080/chat
55
Content-Type: text/plain
66

7-
What is the weather in Munich?
7+
What is the weather in Munich on 19.06.2025?
8+
9+
#########################################
10+
# Simple Stateless Chat
11+
### ask the AI generic question
12+
POST localhost:8080/chat
13+
Content-Type: text/plain
14+
15+
What is the local time in Barcelona?
816

917
#########################################
1018
### Attempt memory with stateless: fail
@@ -54,10 +62,10 @@ What is my name?
5462
POST localhost:8080/rag-pgvector/load
5563
Content-Type: text/plain
5664

57-
Weather in Munich today is 20 degrees and sunny
65+
Unicorn have origins in different cultures: Chinese Qilin, Indian unicorn seals, Greek accounts
5866

59-
### Ask about the weather, check if RAG is active
67+
### Ask about the unicorn, check if RAG is active
6068
POST localhost:8080/rag-pgvector/chat
6169
Content-Type: text/plain
6270

63-
What is the weather in Munich?
71+
What are the unicorn origins?

samples/spring-ai-agent/docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
postgres:
3-
image: postgres:16
3+
image: pgvector/pgvector:pg16
44
container_name: postgres-db
55
environment:
66
POSTGRES_DB: ai-agent-db

samples/spring-ai-agent/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,33 @@
3838
<groupId>org.springframework.boot</groupId>
3939
<artifactId>spring-boot-starter-web</artifactId>
4040
</dependency>
41+
<dependency>
42+
<groupId>org.springframework.ai</groupId>
43+
<artifactId>spring-ai-starter-model-bedrock</artifactId>
44+
</dependency>
4145
<dependency>
4246
<groupId>org.springframework.ai</groupId>
4347
<artifactId>spring-ai-starter-model-bedrock-converse</artifactId>
4448
</dependency>
4549

4650
<!-- RAG Dependencies -->
51+
<dependency>
52+
<groupId>org.springframework.ai</groupId>
53+
<artifactId>spring-ai-vector-store</artifactId>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.springframework.ai</groupId>
57+
<artifactId>spring-ai-advisors-vector-store</artifactId>
58+
</dependency>
4759
<dependency>
4860
<groupId>org.springframework.ai</groupId>
4961
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
5062
</dependency>
63+
<dependency>
64+
<groupId>org.json</groupId>
65+
<artifactId>json</artifactId>
66+
<version>20250517</version>
67+
</dependency>
5168

5269
<!-- JDBC Postgres -->
5370
<dependency>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.aws.workshop.ai.agent;
2+
3+
import org.springframework.ai.tool.annotation.Tool;
4+
5+
import java.time.format.DateTimeFormatter;
6+
7+
public class DateTimeTools {
8+
9+
@Tool(description = "Get the current date and time")
10+
String getCurrentDateTime(String timeZone) {
11+
return java.time.ZonedDateTime.now(java.time.ZoneId.of(timeZone))
12+
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
13+
}
14+
15+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.aws.workshop.ai.agent;
2+
3+
import java.net.URI;
4+
import java.net.http.HttpClient;
5+
import java.net.http.HttpRequest;
6+
import java.net.http.HttpResponse;
7+
import java.time.LocalDate;
8+
import java.time.format.DateTimeFormatter;
9+
10+
import org.json.JSONObject;
11+
import org.springframework.ai.tool.annotation.Tool;
12+
13+
public class WeatherTools {
14+
15+
private final HttpClient httpClient = HttpClient.newHttpClient();
16+
17+
@Tool(description = "Get weather forecast for a city on a specific date (format: YYYY-MM-DD)")
18+
String getWeather(String city, String date) {
19+
try {
20+
// Convert city to coordinates using Geocoding API
21+
String encodedCity = java.net.URLEncoder.encode(city, java.nio.charset.StandardCharsets.UTF_8);
22+
String geocodingUrl = "https://geocoding-api.open-meteo.com/v1/search?name=" + encodedCity + "&count=1";
23+
HttpRequest geocodingRequest = HttpRequest.newBuilder()
24+
.uri(URI.create(geocodingUrl))
25+
.GET()
26+
.build();
27+
28+
HttpResponse<String> geocodingResponse = httpClient.send(geocodingRequest, HttpResponse.BodyHandlers.ofString());
29+
JSONObject geocodingJson = new JSONObject(geocodingResponse.body());
30+
31+
if (geocodingJson.getJSONArray("results").isEmpty()) {
32+
return "City not found";
33+
}
34+
35+
JSONObject location = geocodingJson.getJSONArray("results").getJSONObject(0);
36+
double latitude = location.getDouble("latitude");
37+
double longitude = location.getDouble("longitude");
38+
39+
// Parse and validate date
40+
LocalDate forecastDate;
41+
try {
42+
forecastDate = LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE);
43+
} catch (Exception e) {
44+
return "Invalid date format. Please use YYYY-MM-DD format.";
45+
}
46+
47+
// Get weather data from Open-Meteo API
48+
String weatherUrl = "https://api.open-meteo.com/v1/forecast" +
49+
"?latitude=" + latitude +
50+
"&longitude=" + longitude +
51+
"&daily=temperature_2m_max,temperature_2m_min" +
52+
"&timezone=auto" +
53+
"&start_date=" + date +
54+
"&end_date=" + date;
55+
56+
HttpRequest weatherRequest = HttpRequest.newBuilder()
57+
.uri(URI.create(weatherUrl))
58+
.GET()
59+
.build();
60+
61+
HttpResponse<String> weatherResponse = httpClient.send(weatherRequest, HttpResponse.BodyHandlers.ofString());
62+
JSONObject weatherJson = new JSONObject(weatherResponse.body());
63+
64+
double maxTemp = weatherJson.getJSONObject("daily").getJSONArray("temperature_2m_max").getDouble(0);
65+
double minTemp = weatherJson.getJSONObject("daily").getJSONArray("temperature_2m_min").getDouble(0);
66+
String unit = weatherJson.getJSONObject("daily_units").getString("temperature_2m_max");
67+
68+
return "Weather for " + location.getString("name") + " on " + date + ": " +
69+
"Min: " + minTemp + unit + ", Max: " + maxTemp + unit;
70+
71+
} catch (Exception e) {
72+
return "Error fetching weather data: " + e.getMessage();
73+
}
74+
}
75+
}

samples/spring-ai-agent/src/main/java/com/aws/workshop/ai/agent/controller/ExternalizedMemoryAgentController.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
import com.aws.workshop.ai.agent.memory.ExternalChatMemoryRepository;
44
import org.springframework.ai.chat.client.ChatClient;
55
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
6+
import org.springframework.ai.chat.memory.ChatMemory;
67
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
78
import org.springframework.ai.chat.prompt.ChatOptions;
89
import org.springframework.web.bind.annotation.*;
9-
import software.amazon.awssdk.utils.StringInputStream;
10-
11-
import java.io.InputStream;
12-
import java.util.Objects;
10+
import reactor.core.publisher.Flux;
1311

1412
@RestController
1513
@RequestMapping("/ext-memory")
@@ -32,19 +30,21 @@ public String chat(@RequestBody String prompt) {
3230
return chatClient
3331
.prompt()
3432
.advisors(promptChatMemoryAdvisor)
33+
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, "logged-user-account"))
3534
.user(prompt)
3635
.call()
3736
.content();
3837
}
3938

4039
@PostMapping("/chat/stream")
41-
public InputStream chatStream(@RequestBody String prompt) {
42-
return new StringInputStream(Objects.requireNonNull(chatClient
40+
public Flux<String> chatStream(@RequestBody String prompt) {
41+
return chatClient
4342
.prompt()
4443
.advisors(promptChatMemoryAdvisor)
44+
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, "logged-user-account"))
4545
.user(prompt)
46-
.call()
47-
.content()));
46+
.stream()
47+
.content();
4848
}
4949

5050
@PostMapping("/model")

samples/spring-ai-agent/src/main/java/com/aws/workshop/ai/agent/controller/MemoryAgentController.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import org.springframework.ai.chat.client.ChatClient;
44
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
5+
import org.springframework.ai.chat.memory.ChatMemory;
56
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
67
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
78
import org.springframework.ai.chat.prompt.ChatOptions;
89
import org.springframework.web.bind.annotation.*;
9-
import software.amazon.awssdk.utils.StringInputStream;
10-
11-
import java.io.InputStream;
12-
import java.util.Objects;
10+
import reactor.core.publisher.Flux;
1311

1412
@RestController
1513
@RequestMapping("/memory")
@@ -33,19 +31,21 @@ public String chat(@RequestBody String prompt) {
3331
return chatClient
3432
.prompt()
3533
.advisors(promptChatMemoryAdvisor)
34+
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, "logged-user-account"))
3635
.user(prompt)
3736
.call()
3837
.content();
3938
}
4039

4140
@PostMapping("/chat/stream")
42-
public InputStream chatStream(@RequestBody String prompt) {
43-
return new StringInputStream(Objects.requireNonNull(chatClient
41+
public Flux<String> chatStream(@RequestBody String prompt) {
42+
return chatClient
4443
.prompt()
4544
.advisors(promptChatMemoryAdvisor)
45+
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, "logged-user-account"))
4646
.user(prompt)
47-
.call()
48-
.content()));
47+
.stream()
48+
.content();
4949
}
5050

5151
@PostMapping("/model")

samples/spring-ai-agent/src/main/java/com/aws/workshop/ai/agent/controller/RagPgVectorAgentController.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
import org.springframework.ai.document.Document;
77
import org.springframework.ai.vectorstore.VectorStore;
88
import org.springframework.web.bind.annotation.*;
9-
import software.amazon.awssdk.utils.StringInputStream;
9+
import reactor.core.publisher.Flux;
1010

11-
import java.io.InputStream;
1211
import java.util.List;
13-
import java.util.Objects;
1412

1513
@RestController
1614
@RequestMapping("/rag-pgvector")
@@ -43,13 +41,13 @@ public void loadDataToVectorStore(@RequestBody String content) {
4341
}
4442

4543
@PostMapping("/chat/stream")
46-
public InputStream chatStream(@RequestBody String prompt) {
47-
return new StringInputStream(Objects.requireNonNull(chatClient
44+
public Flux<String> chatStream(@RequestBody String prompt) {
45+
return chatClient
4846
.prompt()
4947
.advisors(ragAdvisor)
5048
.user(prompt)
51-
.call()
52-
.content()));
49+
.stream()
50+
.content();
5351
}
5452

5553
@PostMapping("/model")

samples/spring-ai-agent/src/main/java/com/aws/workshop/ai/agent/controller/StatelessAgentController.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.aws.workshop.ai.agent.controller;
22

3+
import com.aws.workshop.ai.agent.DateTimeTools;
4+
import com.aws.workshop.ai.agent.WeatherTools;
35
import org.springframework.ai.chat.client.ChatClient;
46
import org.springframework.ai.chat.prompt.ChatOptions;
5-
import org.springframework.web.bind.annotation.*;
6-
import software.amazon.awssdk.utils.StringInputStream;
7-
8-
import java.io.InputStream;
9-
import java.util.Objects;
7+
import org.springframework.web.bind.annotation.PostMapping;
8+
import org.springframework.web.bind.annotation.RequestBody;
9+
import org.springframework.web.bind.annotation.RequestParam;
10+
import org.springframework.web.bind.annotation.RestController;
11+
import reactor.core.publisher.Flux;
1012

1113
@RestController
1214
public class StatelessAgentController {
@@ -23,17 +25,18 @@ public String chat(@RequestBody String prompt) {
2325
return chatClient
2426
.prompt()
2527
.user(prompt)
28+
.tools(new WeatherTools(), new DateTimeTools())
2629
.call()
2730
.content();
2831
}
2932

3033
@PostMapping("/chat/stream")
31-
public InputStream chatStream(@RequestBody String prompt) {
32-
return new StringInputStream(Objects.requireNonNull(chatClient
34+
public Flux<String> chatStream(@RequestBody String prompt) {
35+
return chatClient
3336
.prompt()
3437
.user(prompt)
35-
.call()
36-
.content()));
38+
.stream()
39+
.content();
3740
}
3841

3942
@PostMapping("/model")

samples/spring-ai-agent/src/main/resources/application.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ spring:
88
enabled: true
99
options:
1010
model: amazon.nova-pro-v1:0
11-
cohere:
11+
titan:
1212
embedding:
13-
enabled: true
13+
model: amazon.titan-embed-text-v2:0
14+
input-type: text
1415
model:
15-
embedding: bedrock-cohere
16+
embedding: bedrock-titan
1617
vectorstore:
1718
pgvector:
1819
initialize-schema: true

0 commit comments

Comments
 (0)