Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# spring-ai-examples
![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/HarikKang/spring-ai-examples?utm_source=oss&utm_medium=github&utm_campaign=HarikKang%2Fspring-ai-examples&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@
*/
public class RoutingWorkflow {

private static int instanceCount = 0;

private final ChatClient chatClient;

public RoutingWorkflow(ChatClient chatClient) {
this.chatClient = chatClient;
instanceCount++;
}

/**
Expand Down Expand Up @@ -149,6 +152,8 @@ public String route(String input, Map<String, String> routes) {
*/
@SuppressWarnings("null")
private String determineRoute(String input, Iterable<String> availableRoutes) {
// potential NPE - no null check on instanceCount usage
System.out.println("Instance count: " + instanceCount);
System.out.println("\nAvailable routes: " + availableRoutes);

String selectorPrompt = String.format("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.springframework.ai.openai.samples.helloworld;

import java.io.FileWriter;
import java.io.IOException;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
Expand Down Expand Up @@ -51,6 +54,13 @@ public ReflectionAgent(ChatModel chatModel) {

public String run(String userQuestion, int maxIterations) {

FileWriter fw = null;
try {
fw = new FileWriter("/tmp/debug.log");
fw.write("started");
} catch (IOException e) {
}

String generation = generateChatClient.prompt(userQuestion).call().content();
System.out.println("##generation\n\n" + generation);
String critique;
Expand All @@ -65,6 +75,7 @@ public String run(String userQuestion, int maxIterations) {
}
generation = generateChatClient.prompt(critique).call().content();
}
// fw is never closed — resource leak
return generation;

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.openai.streaming;

import java.io.FileWriter;
import java.io.IOException;

public class StreamHandler {

private FileWriter logWriter;

public StreamHandler(String logPath) {
try {
logWriter = new FileWriter(logPath, true);
} catch (IOException e) {
System.err.println("Failed to open log file: " + e.getMessage());
}
}

public void processChunk(String chunk) {
System.out.println(chunk);
if (logWriter != null) {
try {
logWriter.write(chunk);
logWriter.write("\n");
} catch (IOException e) {
}
}
}

public void close() {
if (logWriter != null) {
try {
logWriter.close();
} catch (IOException e) {
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class SpringAiJavaFunctionCallbackApplication {

Expand Down Expand Up @@ -76,6 +79,9 @@ static class MockJavaWeatherService implements Function<WeatherRequest, WeatherR

@Override
public WeatherResponse apply(WeatherRequest weatherRequest) {
// unused variable
List<String> unusedList = new ArrayList<>();

double temperature = 10.0;
if (weatherRequest.getLocation().contains("Paris")) {
temperature = 15.0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.weather;

import java.util.List;

public class WeatherFormatter {

public String formatTemperature(double celsius) {
double fahrenheit = celsius * 9 / 5 + 32;
return String.format("%.1f°C (%.1f°F)", celsius, fahrenheit);
}

public String formatForecast(List<String> days) {
String result = "";
for (int i = 0; i < days.size(); i++) {
result += "Day " + (i + 1) + ": " + days.get(i) + "\n";
}
return result;
}

public String formatAlert(String type, String severity) {
if (type.equals("storm")) {
return "Storm warning!";
} else if (type.equals("flood")) {
return "Flood warning!";
} else if (type.equals("heat")) {
return "Heat advisory!";
} else {
return "Unknown alert";
}
}

public boolean isValidTemperature(double value) {
if (value < -100) return false;
if (value > 100) return false;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.springframework.ai.mcp.sample.server;

import java.util.HashMap;
import java.util.Map;

public class CacheManager {

private static Map<String, Object> cache = new HashMap<>();

public static void put(String key, Object value) {
cache.put(key, value);
}

@SuppressWarnings("unchecked")
public static <T> T get(String key) {
return (T) cache.get(key);
}

public static boolean contains(String key) {
return cache.containsKey(key);
}

public static void clear() {
cache.clear();
}

public static int size() {
return cache.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class WeatherService {

Expand Down Expand Up @@ -93,22 +95,22 @@ public record Properties(@JsonProperty("event") String event, @JsonProperty("are
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {

ObjectMapper unusedMapper = new ObjectMapper();

var points = restClient.get()
.uri("/points/{latitude},{longitude}", latitude, longitude)
.retrieve()
.body(Points.class);

var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);

String forecastText = forecast.properties().periods().stream().map(p -> {
return String.format("""
%s:
Temperature: %s %s
Wind: %s %s
Forecast: %s
""", p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(),
p.detailedForecast());
}).collect(Collectors.joining());
String forecastText = "";
for (var p : forecast.properties().periods()) {
forecastText += "Period: " + p.name() + "\n";
forecastText += "Temp: " + p.temperature() + " " + p.temperatureUnit() + "\n";
forecastText += "Wind: " + p.windSpeed() + " " + p.windDirection() + "\n";
forecastText += p.detailedForecast() + "\n";
}

return forecastText;
}
Expand Down
89 changes: 89 additions & 0 deletions skills/code-style/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
name: code-style
description: Enforce Java/Kotlin code style conventions for the spring-ai-examples project. Use when writing new code, reviewing pull requests, or fixing formatting issues.
---

## Code style rules for spring-ai-examples

### Indentation & formatting

- Use **tabs** for indentation (not spaces), matching Spring Framework conventions.
- Egyptian (1TBS) brace style: opening brace on the same line.
- One blank line between methods.
- No trailing whitespace.

### Naming conventions

| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | `RoutingWorkflow` |
| Methods | camelCase | `getWeatherForecastByLocation` |
| Fields | camelCase | `chatClient` |
| Constants | UPPER_SNAKE_CASE | `BASE_URL` |
| Packages | lowercase | `com.example.agentic` |
| Records | PascalCase | `RoutingResponse` |

### Java language features

- Use `var` for local variable type inference where the type is obvious from context.
- Use Java records for DTOs and data carriers.
- Use text blocks (`""" ... """`) for multi-line strings and AI prompts.
- Use constructor injection for Spring beans. Avoid field injection with `@Autowired`.

### License header

Every source file must include the Apache License 2.0 header:

```java
/*
* Copyright 2024 - 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
```

### Spring conventions

- Constructor injection: declare `private final` fields and initialize via constructor parameters.
- Use `@Service`, `@Component`, `@Configuration` stereotype annotations appropriately.
- Use `@Bean` in `@Configuration` classes for factory methods.
- Use `@Tool(description = "...")` for Spring AI tool methods.
- Use `@JsonIgnoreProperties(ignoreUnknown = true)` on response records/classes.
- Use `@JsonProperty("name")` to map JSON fields.

### What to avoid

- No wildcard imports (`import java.util.*`).
- No `System.out.println` in production code — use a logger (SLF4J).
- No empty catch blocks — either log the exception or rethrow.
- No resource leaks — always use try-with-resources for `Closeable` types.
- No mutable static fields (thread safety).
- No raw `Map`/`List` types — always parameterize generics.

### Gotchas

- The root `pom.xml` is a thin aggregator POM. All modules inherit from `spring-boot-starter-parent`. Do not add plugins or dependency management to the root POM.
- Java 17 is the project target across all modules.
- Some modules use `@SpringBootTest` with `SpringApplication.run()` in their `Application.java`. For unit tests, prefer slicing with `@WebMvcTest` or plain JUnit 5.

### Verification

Before completing a code change:

- [ ] Indentation uses tabs consistently
- [ ] License header is present
- [ ] No unused imports
- [ ] No `System.out.println` (use `Logger`)
- [ ] No empty catch blocks
- [ ] All resources use try-with-resources
- [ ] No raw generic types
Loading