Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class OpenAiChatAutoConfiguration {

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.ai.openai", name = { "api-key", "chat.api-key" },
matchIfMissing = false)
public OpenAiApi openAiApi(OpenAiConnectionProperties commonProperties, OpenAiChatProperties chatProperties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,24 @@ void openAiApiBean() {
});
}

@Test
void imageOnlyApiKeyDoesNotFailChatAutoConfiguration() {
// Regression test for https://github.com/spring-projects/spring-ai/issues/1818
// When only spring.ai.openai.image.api-key is set (no chat or common api-key),
// OpenAiChatAutoConfiguration must not throw an IllegalArgumentException.
// The OpenAiApi bean for chat should simply not be created.
new ApplicationContextRunner()
.withPropertyValues("spring.ai.openai.image.api-key=IMAGE_API_KEY",
"spring.ai.openai.base-url=TEST_BASE_URL")
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class,
OpenAiImageAutoConfiguration.class, RestClientAutoConfiguration.class,
SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class,
WebClientAutoConfiguration.class))
.run(context -> {
assertThat(context).hasNotFailed();
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,31 @@ private static boolean hasBidirectionalParameters(Method method) {
return false;
}

private static String bidirectionalParamNames(Method method) {
StringBuilder sb = new StringBuilder();
for (Class<?> paramType : method.getParameterTypes()) {
if (McpSyncRequestContext.class.isAssignableFrom(paramType)
|| McpAsyncRequestContext.class.isAssignableFrom(paramType)
|| McpSyncServerExchange.class.isAssignableFrom(paramType)
|| McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
if (!sb.isEmpty()) {
sb.append(", ");
}
sb.append(paramType.getSimpleName());
}
}
return sb.toString();
}

public static Predicate<Method> filterMethodWithBidirectionalParameters() {
return method -> {
if (!hasBidirectionalParameters(method)) {
return true;
}
logger.warn(
"Stateless servers doesn't support bidirectional parameters. Skipping method {} with bidirectional parameters",
method);
logger
.warn("Skipping MCP tool method '{}.{}' — stateless servers do not support bidirectional parameters ({}). "
+ "To use this tool, switch to a stateful server (McpServerTransportProvider with session support).",
method.getDeclaringClass().getSimpleName(), method.getName(), bidirectionalParamNames(method));
return false;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,13 @@ private String unescapeStringValue(String in) {

@Override
public Filter.Operand visitIntegerConstant(FiltersParser.IntegerConstantContext ctx) {
return new Filter.Value(Integer.valueOf(ctx.getText()));
String text = ctx.getText();
try {
return new Filter.Value(Integer.parseInt(text));
}
catch (NumberFormatException e) {
return new Filter.Value(Long.parseLong(text));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,20 @@ public void testLong() {
assertThat(exp3).isEqualTo(new Expression(EQ, new Key("biz_id"), new Value(-5L)));
}

@Test
public void testLargeIntegerFallsBackToLong() {
// Values exceeding Integer.MAX_VALUE (2147483647) should be parsed as Long
Expression exp1 = this.parser.parse("id == 9223372036854775807");
assertThat(exp1).isEqualTo(new Expression(EQ, new Key("id"), new Value(Long.MAX_VALUE)));

Expression exp2 = this.parser.parse("id == 2147483648");
assertThat(exp2).isEqualTo(new Expression(EQ, new Key("id"), new Value(2147483648L)));

// Values within Integer range should still parse as Integer
Expression exp3 = this.parser.parse("year == 2020");
assertThat(exp3).isEqualTo(new Expression(EQ, new Key("year"), new Value(2020)));
}

@Test
public void testIdentifiers() {
Expression exp = this.parser.parse("'country.1' == 'BG'");
Expand Down