Skip to content

Bedrock/Converse Chat Model Does not Properly Stream Tools When Using Suppliers vs Functions #1878

Open
@gm2552

Description

@gm2552

Bug description
When attempting to use a tools along with streaming, I am using a java.util.function.Supplier as my FunctionCallback (vs a java.util.function.Function); I am using BedrockProxyChatModel as my ChatModel.

The initial bedrock LLM response correctly selects my Supplier as the tool choice, however to tool is never called by Spring AI. This appears to only happen with Suppliers; java.util.function.Function works as expected.

While debugging, the issue a manifested in the isToolCall of the org.springframework.ai.chat.model.AbstractToolCallSupport class when after the following debug message.

DEBUG 34272 ---  [tyEventLoop-2-2] o.s.a.b.converse.BedrockProxyChatModel   : Received converse stream output:MessageStopEvent(StopReason=tool_use)

The chatResponse contains no generations, therefor the tool is not executed.

	protected boolean isToolCall(ChatResponse chatResponse, Set<String> toolCallFinishReasons) {
		Assert.isTrue(!CollectionUtils.isEmpty(toolCallFinishReasons), "Tool call finish reasons cannot be empty!");
		if (chatResponse == null) {
			return false;
		}
		var generations = chatResponse.getResults();
		if (CollectionUtils.isEmpty(generations)) {
			return false;
		}
		return generations.stream().anyMatch(g -> isToolCall(g, toolCallFinishReasons));
	}

Environment
This was encountered using spring-ai M4 along with Java 21.

Steps to reproduce
The following is an excerpt from a trivial SpringBoot application that can reproduce the problem when Spring AI is configured use the Bedrock/Converse chat models. It includes a very simple System.out to view the output.

	@Bean
	public CommandLineRunner commandLineRunner(ChatClient chatClient) {
		
		return (args) -> {
			
            FunctionCallback.Builder builder =
                    FunctionCallback.builder();
            
            FunctionCallback functionCallBack = builder.description("Get the weather")
            .function("weather", new WeatherService())
            .build();
            
			
			ChatClientRequestSpec spec = chatClient.prompt();
			
			spec.functions(functionCallBack).user("What's the weather like").stream().chatResponse()
			.doOnNext(resp -> {
				if (resp.getResult() != null)
					System.out.println(resp.getResult().getOutput().getContent());
				}).collectList().block();			

		};
	}
	
	public static class WeatherService implements Supplier<WeatherService.Response> {

		public record Response(double temp) {}
	
		public Response get() {
			return new Response(30.0);
		}
		
	}

Expected behavior
I expect an output something similar to "The current weather is 30 degrees".

Minimal Complete Reproducible example
See the steps to reproduce for a code sample.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions