Describe the bug
OllamaChatModel.doStream ignores the ModelCreationContext.stream flag and always uses Ollama's streaming API, regardless of what the caller requests. When a caller consumes the model via model.stream(...).blockLast() (a common pattern for single-turn clients and inside ReActAgent reasoning), it receives an empty response with no error thrown.
Because Model.stream(...) is final in ChatModelBase and always dispatches to doStream, there is no way for a caller to opt into the non-streaming path — the stream=false setting silently has no effect.
To Reproduce
Steps to reproduce the behavior:
- Your code — resolve an Ollama model with
stream=false, then consume via blockLast():
import io.agentscope.core.model.Model;
import io.agentscope.core.model.ModelCreationContext;
import io.agentscope.core.model.ModelRegistry;
import io.agentscope.core.model.ChatResponse;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import java.util.List;
ModelCreationContext ctx = ModelCreationContext.builder()
.baseUrl("http://localhost:11434")
.stream(false) // request NON-streaming
.build();
Model model = ModelRegistry.resolve("ollama:qwen2.5", ctx);
ChatResponse resp = model.stream(
List.of(Msg.builder().role(MsgRole.USER).textContent("Hello").build()),
null, null).blockLast();
System.out.println("content = " + resp.getContent());
-
How to execute — with a real Ollama server running at localhost:11434 (or against a mocked HttpTransport returning Ollama's chunked streaming format). The snippet above hits the model's stream() entry point.
-
See error — no exception is thrown, but the printed content is empty:
This is because under streaming mode, blockLast() returns the last streaming chunk, which for Ollama is the trailing {"done":true} marker carrying no message.content. The full reply is silently lost.
Expected behavior
When ModelCreationContext.stream is false, OllamaChatModel should use Ollama's non-streaming API (/api/chat with stream:false, returning one complete response) so that model.stream(...).blockLast() returns the full reply — mirroring how OpenAIChatModel already honors this flag (in OpenAIChatModel.doStream0, stream=false returns Flux.just(fullResponse)).
When the flag is null/unset, current streaming behavior should be preserved (backward compatibility).
Error messages
No error message is thrown — this is a silent failure (empty content), which is what makes it hard to diagnose. The root cause is visible in the source:
// OllamaChatModel.java — doStream hardcodes the streaming branch
@Override
protected Flux<ChatResponse> doStream(
List<Msg> messages, List<ToolSchema> tools, GenerateOptions options) {
return streamWithHttpClient(
messages, tools,
options != null ? options.getToolChoice() : null,
OllamaOptions.fromGenerateOptions(options),
true); // ← hardcoded; ignores ModelCreationContext.stream
}
Environment (please complete the following information):
- AgentScope-Java Version:
2.0.1-SNAPSHOT (confirmed also reproducible on current main branch)
- Java Version: 17
- OS: Windows (reproduced locally); also confirmed on Linux CI
Additional context
ModelCreationContext documents stream as a provider-neutral field ("common enough to be understood by all providers"), and OpenAIChatModel honors it end-to-end. OllamaChatModel currently does not — four gaps combine to break the contract:
OllamaModelProvider.create never reads context.getStream()
OllamaChatModel has no stream field
OllamaChatModel.Builder has no stream(boolean) method
doStream hardcodes the literal true
Notably, the non-streaming branch already exists in streamWithHttpClient(..., false) (it calls httpClient.chat() and returns a single complete response) — it is simply unreachable from doStream.
Impact: silent failure (no exception, empty content) for any caller consuming OllamaChatModel via stream().blockLast(), including single-turn bots and ReActAgent reasoning internals.
I have a fix ready that mirrors the OpenAIChatModel implementation (honor the flag end-to-end: provider reads context.getStream() → builder field → doStream dispatches on it) and will open a PR linked to this issue.
Describe the bug
OllamaChatModel.doStreamignores theModelCreationContext.streamflag and always uses Ollama's streaming API, regardless of what the caller requests. When a caller consumes the model viamodel.stream(...).blockLast()(a common pattern for single-turn clients and insideReActAgentreasoning), it receives an empty response with no error thrown.Because
Model.stream(...)isfinalinChatModelBaseand always dispatches todoStream, there is no way for a caller to opt into the non-streaming path — thestream=falsesetting silently has no effect.To Reproduce
Steps to reproduce the behavior:
stream=false, then consume viablockLast():How to execute — with a real Ollama server running at
localhost:11434(or against a mockedHttpTransportreturning Ollama's chunked streaming format). The snippet above hits the model'sstream()entry point.See error — no exception is thrown, but the printed content is empty:
This is because under streaming mode,
blockLast()returns the last streaming chunk, which for Ollama is the trailing{"done":true}marker carrying nomessage.content. The full reply is silently lost.Expected behavior
When
ModelCreationContext.streamisfalse,OllamaChatModelshould use Ollama's non-streaming API (/api/chatwithstream:false, returning one complete response) so thatmodel.stream(...).blockLast()returns the full reply — mirroring howOpenAIChatModelalready honors this flag (inOpenAIChatModel.doStream0,stream=falsereturnsFlux.just(fullResponse)).When the flag is
null/unset, current streaming behavior should be preserved (backward compatibility).Error messages
No error message is thrown — this is a silent failure (empty content), which is what makes it hard to diagnose. The root cause is visible in the source:
Environment (please complete the following information):
2.0.1-SNAPSHOT(confirmed also reproducible on currentmainbranch)Additional context
ModelCreationContextdocumentsstreamas a provider-neutral field ("common enough to be understood by all providers"), andOpenAIChatModelhonors it end-to-end.OllamaChatModelcurrently does not — four gaps combine to break the contract:OllamaModelProvider.createnever readscontext.getStream()OllamaChatModelhas nostreamfieldOllamaChatModel.Builderhas nostream(boolean)methoddoStreamhardcodes the literaltrueNotably, the non-streaming branch already exists in
streamWithHttpClient(..., false)(it callshttpClient.chat()and returns a single complete response) — it is simply unreachable fromdoStream.Impact: silent failure (no exception, empty content) for any caller consuming
OllamaChatModelviastream().blockLast(), including single-turn bots andReActAgentreasoning internals.I have a fix ready that mirrors the
OpenAIChatModelimplementation (honor the flag end-to-end: provider readscontext.getStream()→ builder field →doStreamdispatches on it) and will open a PR linked to this issue.