Skip to content

Commit b5f7c34

Browse files
committed
GH-1857: make length for tool description validation configurable
1 parent f209065 commit b5f7c34

File tree

9 files changed

+128
-13
lines changed

9 files changed

+128
-13
lines changed

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017, 2025 Pivotal, Inc.
2+
* Copyright (c) 2017, 2026 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -62,5 +62,7 @@ public class Constants {
6262

6363
public static final String PREF_AI_MCP_ENABLED = "boot-java.ai.mcp-server-enabled";
6464
public static final String PREF_AI_MCP_PORT = "boot-java.ai.mcp-server-port";
65+
66+
public static final String PREF_SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH = "boot-java.spring-ai.validation.tool-description-minimum-length";
6567

6668
}

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017, 2025 Pivotal, Inc.
2+
* Copyright (c) 2017, 2026 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -264,6 +264,12 @@ private void sendConfiguration() {
264264
"mcp-server-port", preferenceStore.getBoolean(Constants.PREF_AI_MCP_PORT)
265265
)
266266
);
267+
268+
bootJavaObj.put("spring-ai", Map.of(
269+
"validation", Map.of(
270+
"tool-description-minimum-length", preferenceStore.getInt(Constants.PREF_SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH)
271+
)
272+
));
267273

268274

269275
settings.put("boot-java", bootJavaObj);

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/AiConfigPreferencePage.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2025 Broadcom
2+
* Copyright (c) 2025, 2026 Broadcom
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -15,6 +15,7 @@
1515
import org.eclipse.jface.dialogs.MessageDialog;
1616
import org.eclipse.jface.preference.BooleanFieldEditor;
1717
import org.eclipse.jface.preference.FieldEditorPreferencePage;
18+
import org.eclipse.jface.preference.IntegerFieldEditor;
1819
import org.eclipse.jface.preference.StringFieldEditor;
1920
import org.eclipse.jface.util.PropertyChangeEvent;
2021
import org.eclipse.swt.widgets.Composite;
@@ -58,6 +59,13 @@ protected void createFieldEditors() {
5859

5960
// Initialize the enabled state of the port field
6061
updateMcpPortFieldState();
62+
63+
IntegerFieldEditor minDescLengthEditor = new IntegerFieldEditor(
64+
Constants.PREF_SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH,
65+
"Minimum length for @Tool/@McpTool description",
66+
fieldEditorParent);
67+
minDescLengthEditor.setValidRange(1, Integer.MAX_VALUE);
68+
addField(minDescLengthEditor);
6169
}
6270

6371
@Override

eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017, 2025 Pivotal, Inc.
2+
* Copyright (c) 2017, 2026 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -57,6 +57,8 @@ public void initializeDefaultPreferences() {
5757

5858
preferenceStore.setDefault(Constants.PREF_AI_MCP_ENABLED, false);
5959
preferenceStore.setDefault(Constants.PREF_AI_MCP_PORT, 50627);
60+
61+
preferenceStore.setDefault(Constants.PREF_SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH, 30);
6062
}
6163

6264
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,22 @@ public class BootJavaConfig implements InitializingBean {
5050

5151
public static final boolean VALIDAITON_SPEL_EXPRESSIONS_ENABLED_DEFAULT = true;
5252

53+
public static final int SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH_DEFAULT = 30;
54+
5355

5456
private final SimpleWorkspaceService workspace;
5557
private Settings settings = new Settings(null);
5658
private ListenerList<Void> listeners = new ListenerList<Void>();
5759

58-
BootJavaConfig(SimpleLanguageServer server) {
60+
public BootJavaConfig(SimpleLanguageServer server) {
5961
this.workspace = server.getWorkspaceService();
6062
}
6163

64+
public int getSpringAiToolDescriptionMinimumLength() {
65+
Integer len = settings.getInt("boot-java", "spring-ai", "validation", "tool-description-minimum-length");
66+
return len != null ? len : SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH_DEFAULT;
67+
}
68+
6269
public int getLiveInformationFetchDataMaxRetryCount() {
6370
Integer delay = settings.getInt("boot-java", "live-information", "fetch-data", "max-retries");
6471
return delay != null ? delay.intValue() : LIVE_INFORMATION_FETCH_DATA_RETRY_MAX_NO_DEFAULT;

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ public class JdtConfig {
205205
return new SpringAiDescriptionExistsReconciler();
206206
}
207207

208-
@Bean SpringAiDescriptionTooShortReconciler springAiDescriptionTooShortReconciler() {
209-
return new SpringAiDescriptionTooShortReconciler();
208+
@Bean SpringAiDescriptionTooShortReconciler springAiDescriptionTooShortReconciler(BootJavaConfig config) {
209+
return new SpringAiDescriptionTooShortReconciler(config);
210210
}
211211

212212
@Conditional(LspClient.OnNotEclipseClient.class)

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/SpringAiDescriptionTooShortReconciler.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@
1111
package org.springframework.ide.vscode.boot.java.reconcilers;
1212

1313
import org.eclipse.jdt.core.dom.Annotation;
14+
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
1415
import org.springframework.ide.vscode.boot.java.SpringAiProblemType;
1516
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
1617
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
1718
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
1819

1920
public class SpringAiDescriptionTooShortReconciler extends AbstractSpringAiAnnotationReconciler {
2021

21-
public static final int MIN_DESCRIPTION_LENGTH = 30;
22+
private final BootJavaConfig config;
23+
24+
public SpringAiDescriptionTooShortReconciler(BootJavaConfig config) {
25+
this.config = config;
26+
}
2227

2328
@Override
2429
public ProblemType getProblemType() {
@@ -27,7 +32,7 @@ public ProblemType getProblemType() {
2732

2833
@Override
2934
protected void validateDescription(String description, Annotation node, IProblemCollector problemCollector) {
30-
if (description != null && !description.isBlank() && description.trim().length() < MIN_DESCRIPTION_LENGTH) {
35+
if (description != null && !description.isBlank() && description.trim().length() < config.getSpringAiToolDescriptionMinimumLength()) {
3136
problemCollector.accept(new ReconcileProblemImpl(getProblemType(),
3237
SpringAiProblemType.SPRING_AI_TOOL_DESCRIPTION_TOO_SHORT.getLabel(),
3338
node.getStartPosition(), node.getLength()));

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/SpringAiDescriptionTooShortReconcilerTest.java

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,28 @@
1111
package org.springframework.ide.vscode.boot.java.reconcilers.test;
1212

1313
import static org.junit.jupiter.api.Assertions.assertEquals;
14-
import static org.springframework.ide.vscode.boot.java.reconcilers.SpringAiDescriptionTooShortReconciler.MIN_DESCRIPTION_LENGTH;
14+
import static org.springframework.ide.vscode.boot.app.BootJavaConfig.SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH_DEFAULT;
1515

1616
import java.util.List;
1717

1818
import org.junit.jupiter.api.AfterEach;
1919
import org.junit.jupiter.api.BeforeEach;
2020
import org.junit.jupiter.api.Test;
21+
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
2122
import org.springframework.ide.vscode.boot.java.SpringAiProblemType;
2223
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
2324
import org.springframework.ide.vscode.boot.java.reconcilers.SpringAiDescriptionTooShortReconciler;
25+
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
2426
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
27+
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
28+
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
29+
30+
import com.google.gson.JsonObject;
2531

2632
public class SpringAiDescriptionTooShortReconcilerTest extends BaseReconcilerTest {
2733

34+
private SimpleLanguageServer languageServer;
35+
2836
@Override
2937
protected String getFolder() {
3038
return "springai/shortdesc";
@@ -37,12 +45,13 @@ protected String getProjectName() {
3745

3846
@Override
3947
protected JdtAstReconciler getReconciler() {
40-
return new SpringAiDescriptionTooShortReconciler();
48+
return new SpringAiDescriptionTooShortReconciler(new BootJavaConfig(languageServer));
4149
}
4250

4351
@BeforeEach
4452
void setup() throws Exception {
4553
super.setup();
54+
languageServer = new SimpleLanguageServer("test", null, new LanguageServerProperties());
4655
}
4756

4857
@AfterEach
@@ -52,7 +61,7 @@ void tearDown() throws Exception {
5261

5362
@Test
5463
void toolWithTooShortDescription_shouldWarn() throws Exception {
55-
String shortDesc = "X".repeat(MIN_DESCRIPTION_LENGTH - 1);
64+
String shortDesc = "X".repeat(SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH_DEFAULT - 1);
5665
String source = """
5766
package example.springai;
5867
@@ -77,7 +86,7 @@ public String compute(String input) {
7786

7887
@Test
7988
void toolWithDescriptionAtMinimumLength_shouldNotWarn() throws Exception {
80-
String okDesc = "X".repeat(MIN_DESCRIPTION_LENGTH);
89+
String okDesc = "X".repeat(SPRING_AI_TOOL_DESCRIPTION_MIN_LENGTH_DEFAULT);
8190
String source = """
8291
package example.springai;
8392
@@ -211,6 +220,76 @@ public int add(int a, int b) {
211220
assertEquals(0, problems.size());
212221
}
213222

223+
@Test
224+
void customMinLength_shorterThanDefault_descriptionBetweenCustomAndDefault_shouldNotWarn() throws Exception {
225+
// This description is longer than the custom minimum (10) but shorter than the standard default (30).
226+
// With the custom minimum in effect, it should not warn.
227+
String desc = "X".repeat(20);
228+
String source = """
229+
package example.springai;
230+
231+
import org.springframework.ai.tool.annotation.Tool;
232+
233+
class MyTools {
234+
235+
@Tool(description = "%s")
236+
public String compute(String input) {
237+
return input;
238+
}
239+
240+
}
241+
""".formatted(desc);
242+
243+
List<ReconcileProblem> problems = reconcile(
244+
() -> new SpringAiDescriptionTooShortReconciler(configWithMinLength(10)),
245+
"MyTools.java", source, true);
246+
247+
assertEquals(0, problems.size());
248+
}
249+
250+
@Test
251+
void customMinLength_longerThanDefault_descriptionBetweenDefaultAndCustom_shouldWarn() throws Exception {
252+
// This description is longer than the standard default (30) but shorter than the custom minimum (50).
253+
// With the custom minimum in effect, it should still warn.
254+
String desc = "X".repeat(35);
255+
String source = """
256+
package example.springai;
257+
258+
import org.springframework.ai.tool.annotation.Tool;
259+
260+
class MyTools {
261+
262+
@Tool(description = "%s")
263+
public String compute(String input) {
264+
return input;
265+
}
266+
267+
}
268+
""".formatted(desc);
269+
270+
List<ReconcileProblem> problems = reconcile(
271+
() -> new SpringAiDescriptionTooShortReconciler(configWithMinLength(50)),
272+
"MyTools.java", source, true);
273+
274+
assertEquals(1, problems.size());
275+
assertEquals(SpringAiProblemType.SPRING_AI_TOOL_DESCRIPTION_TOO_SHORT, problems.get(0).getType());
276+
}
277+
278+
private BootJavaConfig configWithMinLength(int minLength) {
279+
JsonObject validation = new JsonObject();
280+
validation.addProperty("tool-description-minimum-length", minLength);
281+
JsonObject springAi = new JsonObject();
282+
springAi.add("validation", validation);
283+
JsonObject bootJava = new JsonObject();
284+
bootJava.add("spring-ai", springAi);
285+
JsonObject root = new JsonObject();
286+
root.add("boot-java", bootJava);
287+
288+
BootJavaConfig config = new BootJavaConfig(languageServer);
289+
config.handleConfigurationChange(new Settings(root));
290+
return config;
291+
}
292+
214293
@Test
215294
void multipleToolsWithMixedDescriptionLengths_shouldWarnForShortOnly() throws Exception {
216295
String source = """

vscode-extensions/vscode-spring-boot/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,12 @@
546546
"type": "integer",
547547
"default": 50627,
548548
"description": "Port used by the embedded MCP serverr"
549+
},
550+
"boot-java.spring-ai.validation.tool-description-minimum-length": {
551+
"type": "integer",
552+
"default": 30,
553+
"minimum": 1,
554+
"description": "Minimum required character length for Spring AI tool descriptions (@Tool, @McpTool, @McpPrompt, @McpResource). Descriptions shorter than this will be reported as a warning."
549555
}
550556
}
551557
},

0 commit comments

Comments
 (0)