Skip to content

Commit 679ad38

Browse files
committed
🚧 Externalize configuration of models
1 parent 5fecd10 commit 679ad38

7 files changed

Lines changed: 74 additions & 141 deletions

chapter1/DungeonMasterSimple.java

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,42 @@
22

33
//JAVA 25+
44
//REPOS mavencentral,spring-milestones=https://repo.spring.io/milestone
5-
//DEPS org.springframework.ai:spring-ai-bedrock-converse:2.0.0-M4
5+
//SOURCES ../config/BedrockChatModelConfig.java
66
//DEPS org.springframework.ai:spring-ai-client-chat:2.0.0-M4
7-
//DEPS software.amazon.awssdk:bedrockruntime:2.41.34
8-
//DEPS software.amazon.awssdk:auth:2.41.34
97
//DEPS org.slf4j:slf4j-api:2.0.17
108
//DEPS org.slf4j:slf4j-simple:2.0.17
119

1210
import org.slf4j.Logger;
1311
import org.slf4j.LoggerFactory;
14-
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
15-
import org.springframework.ai.bedrock.converse.BedrockChatOptions;
16-
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
17-
import software.amazon.awssdk.regions.Region;
18-
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
19-
2012
import org.springframework.ai.chat.client.ChatClient;
2113

2214
private static final Logger log = LoggerFactory.getLogger("DungeonMasterSimple");
2315

2416
void main() {
25-
log.info("=== Starting Dungeon Master AI Agent ===");
26-
27-
// Step 1: Read the Bedrock API key from environment
28-
var bearerToken = System.getenv("AWS_BEARER_TOKEN_BEDROCK");
29-
if (bearerToken == null || bearerToken.isBlank()) {
30-
log.error("Set AWS_BEARER_TOKEN_BEDROCK first — get your key from the Amazon Bedrock Console → API keys → Short-term API keys");
31-
return;
32-
}
33-
34-
// Step 2: Create AWS Bedrock Runtime Client with API key (bearer token auth)
35-
var bedrockClient = BedrockRuntimeClient.builder()
36-
.region(Region.US_WEST_2)
37-
.credentialsProvider(AnonymousCredentialsProvider.create())
38-
.overrideConfiguration(c -> c.putHeader("Authorization", "Bearer " + bearerToken))
39-
.build();
4017

41-
// Step 3: Configure model options (which Claude model to use)
42-
var modelId = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
43-
var options = BedrockChatOptions.builder()
44-
.model(modelId)
45-
.build();
18+
log.info("=== Starting Dungeon Master AI Agent ===");
4619

47-
// Step 4: Create Spring AI ChatModel (wraps Bedrock client)
48-
var chatModel = BedrockProxyChatModel.builder()
49-
.bedrockRuntimeClient(bedrockClient)
50-
.defaultOptions(options)
51-
.build();
20+
var chatModel = BedrockChatModelConfig.createChatModel();
5221

5322
// Step 5: Build ChatClient with system prompt (defines AI personality)
5423
var agent = ChatClient.builder(chatModel)
55-
.defaultSystem("You are a game master for a Dungeon & Dragon game")
24+
.defaultSystem("""
25+
You are a dungeon master for a D&D game.
26+
You describe the environment, creatures, and challenges.
27+
You respond in a terse and humorous way.
28+
""")
5629
.build();
5730

5831
// Step 6: Invoke the AI agent
5932
var playerMessage = "Hi, I am an adventurer ready for adventure!";
6033
log.info("Player: " + playerMessage + "\n");
6134

6235
try {
63-
var response = agent
64-
.prompt()
36+
var response = agent.prompt()
6537
.user(playerMessage)
6638
.call()
6739
.content();
68-
69-
log.info("Dungeon Master says:");
7040
log.info(response);
71-
7241
} catch (Exception e) {
7342
log.error("Error invoking AI agent: {}", e.getMessage());
7443
} finally {

chapter2/DungeonMasterWithBuiltInTools.java

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@
22

33
//JAVA 25+
44
//REPOS mavencentral,spring-milestones=https://repo.spring.io/milestone
5-
//DEPS org.springframework.ai:spring-ai-bedrock-converse:2.0.0-M4
5+
//SOURCES ../config/BedrockChatModelConfig.java
66
//DEPS org.springframework.ai:spring-ai-client-chat:2.0.0-M4
7+
78
//DEPS org.springaicommunity:spring-ai-agent-utils:0.5.0
8-
//DEPS software.amazon.awssdk:bedrockruntime:2.41.34
9-
//DEPS software.amazon.awssdk:auth:2.41.34
9+
1010
//DEPS org.slf4j:slf4j-api:2.0.17
1111
//DEPS org.slf4j:slf4j-simple:2.0.17
1212

1313
import org.slf4j.Logger;
1414
import org.slf4j.LoggerFactory;
15-
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
16-
import org.springframework.ai.bedrock.converse.BedrockChatOptions;
1715
import org.springframework.ai.chat.client.ChatClient;
18-
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
19-
import software.amazon.awssdk.regions.Region;
20-
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
2116

2217
import org.springaicommunity.agent.tools.SmartWebFetchTool;
2318

@@ -26,43 +21,17 @@
2621
void main() {
2722
log.info("=== Starting Dungeon Master AI Agent with Built-in Tools ===");
2823

29-
// Step 1: Read the Bedrock API key from environment
30-
var bearerToken = System.getenv("AWS_BEARER_TOKEN_BEDROCK");
31-
if (bearerToken == null || bearerToken.isBlank()) {
32-
log.error("Set AWS_BEARER_TOKEN_BEDROCK first — get your key from the Amazon Bedrock Console → API keys → Short-term API keys");
33-
return;
34-
}
35-
36-
// Step 2: Create AWS Bedrock Runtime Client with API key (bearer token auth)
37-
var bedrockClient = BedrockRuntimeClient.builder()
38-
.region(Region.US_WEST_2)
39-
.credentialsProvider(AnonymousCredentialsProvider.create())
40-
.overrideConfiguration(c -> c.putHeader("Authorization", "Bearer " + bearerToken))
41-
.build();
42-
43-
// Step 2: Configure model options
44-
var modelId = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
45-
var options = BedrockChatOptions.builder()
46-
.model(modelId)
47-
.build();
24+
var chatModel = BedrockChatModelConfig.createChatModel();
4825

49-
// Step 3: Create Spring AI ChatModel and its associated AI Agent
50-
var chatModel = BedrockProxyChatModel.builder()
51-
.bedrockRuntimeClient(bedrockClient)
52-
.defaultOptions(options)
53-
.build();
5426
var agent = ChatClient.builder(chatModel).build();
55-
27+
5628
// Step 4: Create SmartWebFetchTool tool to enable the AI agent to fetch / process web content
57-
var webFetchTool = SmartWebFetchTool.builder(agent)
58-
.maxContentLength(300_000)
59-
.build();
29+
var webFetch = SmartWebFetchTool.builder(agent).maxContentLength(300_000).build();
6030

6131
try {
6232
// Step 5: Ask the AI to fetch and extract information from Wikipedia
6333
var response = agent.prompt()
6434
.user("Using the website https://en.wikipedia.org/wiki/Dungeons_%26_Dragons tell me the name of the designers of Dungeons and Dragons.")
65-
.tools(webFetchTool)
6635
.call()
6736
.content();
6837

chapter3/DiceTools.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ DiceRollResponse rollDice(
3939

4040
return new DiceRollResponse(rolls, total, description);
4141
}
42-
}
42+
}

chapter3/DungeonMasterWithCustomTools.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,22 @@
11
///usr/bin/env jbang "$0" "$@" ; exit $?
22

33
//JAVA 25+
4-
//SOURCES DiceTools.java
54
//REPOS mavencentral,spring-milestones=https://repo.spring.io/milestone
6-
//DEPS org.springframework.ai:spring-ai-bedrock-converse:2.0.0-M4
5+
//SOURCES DiceTools.java,../config/BedrockChatModelConfig.java
76
//DEPS org.springframework.ai:spring-ai-client-chat:2.0.0-M4
8-
//DEPS software.amazon.awssdk:bedrockruntime:2.41.34
9-
//DEPS software.amazon.awssdk:auth:2.41.34
107
//DEPS org.slf4j:slf4j-api:2.0.17
118
//DEPS org.slf4j:slf4j-simple:2.0.17
129

1310
import org.slf4j.Logger;
1411
import org.slf4j.LoggerFactory;
15-
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
16-
import org.springframework.ai.bedrock.converse.BedrockChatOptions;
1712
import org.springframework.ai.chat.client.ChatClient;
18-
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
19-
import software.amazon.awssdk.regions.Region;
20-
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
2113

2214
private static final Logger log = LoggerFactory.getLogger("DungeonMasterWithCustomTools");
2315

2416
void main() {
2517
log.info("=== Starting Dungeon Master AI Agent with Custom Tools ===");
2618

27-
// Step 1: Read the Bedrock API key from environment
28-
var bearerToken = System.getenv("AWS_BEARER_TOKEN_BEDROCK");
29-
if (bearerToken == null || bearerToken.isBlank()) {
30-
log.error("Set AWS_BEARER_TOKEN_BEDROCK first — get your key from the Amazon Bedrock Console → API keys → Short-term API keys");
31-
return;
32-
}
33-
34-
// Step 2: Create AWS Bedrock Runtime Client with API key (bearer token auth)
35-
var bedrockClient = BedrockRuntimeClient.builder()
36-
.region(Region.US_WEST_2)
37-
.credentialsProvider(AnonymousCredentialsProvider.create())
38-
.overrideConfiguration(c -> c.putHeader("Authorization", "Bearer " + bearerToken))
39-
.build();
40-
41-
// Step 2: Configure model options
42-
var modelId = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
43-
var options = BedrockChatOptions.builder()
44-
.model(modelId)
45-
.build();
46-
47-
// Step 3: Create Spring AI ChatModel
48-
var chatModel = BedrockProxyChatModel.builder()
49-
.bedrockRuntimeClient(bedrockClient)
50-
.defaultOptions(options)
51-
.build();
19+
var chatModel = BedrockChatModelConfig.createChatModel();
5220

5321
// Step 4: Build ChatClient with system prompt AND the custom dice tools
5422
var agent = ChatClient.builder(chatModel)

chapter4/DiceRollMcpServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ DiceRollResponse rollDice(
6060

6161
return new DiceRollResponse(rolls, total, description);
6262
}
63-
}
63+
}

chapter4/DungeonMasterMCPClient.java

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,23 @@
22

33
//JAVA 25+
44
//REPOS mavencentral,spring-milestones=https://repo.spring.io/milestone
5-
//DEPS org.springframework.ai:spring-ai-bedrock-converse:2.0.0-M4
5+
//SOURCES ../config/BedrockChatModelConfig.java
66
//DEPS org.springframework.ai:spring-ai-client-chat:2.0.0-M4
77
//DEPS org.springframework.ai:spring-ai-mcp:2.0.0-M4
88
//DEPS io.modelcontextprotocol.sdk:mcp:1.0.0
9-
//DEPS software.amazon.awssdk:bedrockruntime:2.41.34
10-
//DEPS software.amazon.awssdk:auth:2.41.34
119
//DEPS org.slf4j:slf4j-api:2.0.17
1210
//DEPS org.slf4j:slf4j-simple:2.0.17
1311

1412
import java.util.Set;
1513

1614
import org.slf4j.Logger;
1715
import org.slf4j.LoggerFactory;
18-
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
19-
import org.springframework.ai.bedrock.converse.BedrockChatOptions;
2016
import org.springframework.ai.chat.client.ChatClient;
17+
2118
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
2219
import io.modelcontextprotocol.client.McpClient;
2320
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
2421
import io.modelcontextprotocol.spec.McpSchema;
25-
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
26-
import software.amazon.awssdk.regions.Region;
27-
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
2822

2923
private static final Logger log = LoggerFactory.getLogger("DungeonMasterMCPClient");
3024

@@ -40,12 +34,11 @@ void main() {
4034
log.info("Connecting to D&D Dice Roll MCP Server...");
4135

4236
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:8080")
43-
.endpoint("/mcp")
44-
.build();
37+
.build();
4538

4639
var mcpClient = McpClient.sync(transport)
47-
.clientInfo(new McpSchema.Implementation("dice-mcp-client", "1.0.0"))
48-
.build();
40+
.clientInfo(new McpSchema.Implementation("dice-mcp-client", "1.0.0"))
41+
.build();
4942

5043
try {
5144
mcpClient.initialize();
@@ -61,22 +54,8 @@ void main() {
6154
.build();
6255
var mcpTools = mcpToolProvider.getToolCallbacks();
6356

64-
// Step 4: Create AWS Bedrock ChatModel with API key (bearer token auth)
65-
var bedrockClient = BedrockRuntimeClient.builder()
66-
.region(Region.US_WEST_2)
67-
.credentialsProvider(AnonymousCredentialsProvider.create())
68-
.overrideConfiguration(c -> c.putHeader("Authorization", "Bearer " + bearerToken))
69-
.build();
70-
71-
var modelId = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
72-
var options = BedrockChatOptions.builder()
73-
.model(modelId)
74-
.build();
75-
76-
var chatModel = BedrockProxyChatModel.builder()
77-
.bedrockRuntimeClient(bedrockClient)
78-
.defaultOptions(options)
79-
.build();
57+
// Step 4: Create AWS Bedrock ChatModel
58+
var chatModel = BedrockChatModelConfig.createChatModel();
8059

8160
// Step 5: Build ChatClient with system prompt (tools registered from MCP Server)
8261
var agent = ChatClient.builder(chatModel)

config/BedrockChatModelConfig.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
3+
//JAVA 25+
4+
//DEPS org.springframework.ai:spring-ai-bedrock-converse:2.0.0-M4
5+
//DEPS software.amazon.awssdk:bedrockruntime:2.41.34
6+
//DEPS software.amazon.awssdk:auth:2.41.34
7+
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
11+
import org.springframework.ai.bedrock.converse.BedrockChatOptions;
12+
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
13+
import software.amazon.awssdk.regions.Region;
14+
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
15+
16+
public class BedrockChatModelConfig {
17+
18+
private static final Logger log = LoggerFactory.getLogger("BedrockChatModelConfig");
19+
20+
public static BedrockProxyChatModel createChatModel() {
21+
// Step 1: Read the Bedrock API key from environment
22+
var bearerToken = System.getenv("AWS_BEARER_TOKEN_BEDROCK");
23+
if (bearerToken == null || bearerToken.isBlank()) {
24+
log.error("Set AWS_BEARER_TOKEN_BEDROCK first — get your key from the Amazon Bedrock Console → API keys → Short-term API keys");
25+
throw new IllegalStateException("Missing AWS_BEARER_TOKEN_BEDROCK environment variable");
26+
}
27+
28+
// Step 2: Create AWS Bedrock Runtime Client with API key (bearer token auth)
29+
var bedrockClient = BedrockRuntimeClient.builder()
30+
.region(Region.US_WEST_2)
31+
.credentialsProvider(AnonymousCredentialsProvider.create())
32+
.overrideConfiguration(c -> c.putHeader("Authorization", "Bearer " + bearerToken))
33+
.build();
34+
35+
// Step 3: Configure model options (which Claude model to use)
36+
var modelId = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
37+
var options = BedrockChatOptions.builder()
38+
.model(modelId)
39+
.build();
40+
41+
// Step 4: Create Spring AI ChatModel (wraps Bedrock client)
42+
return BedrockProxyChatModel.builder()
43+
.bedrockRuntimeClient(bedrockClient)
44+
.defaultOptions(options)
45+
.build();
46+
}
47+
48+
}

0 commit comments

Comments
 (0)