Everything is streaming (#7247)
Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -343,7 +343,8 @@ mod tests {
|
||||
use goose::conversation::message::{Message, MessageContent};
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::base::{
|
||||
Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage,
|
||||
stream_from_single_message, MessageStream, Provider, ProviderDef, ProviderMetadata,
|
||||
ProviderUsage, Usage,
|
||||
};
|
||||
use goose::providers::errors::ProviderError;
|
||||
use goose::session::session_manager::SessionType;
|
||||
@@ -385,13 +386,14 @@ mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for MockToolProvider {
|
||||
async fn complete(
|
||||
async fn stream(
|
||||
&self,
|
||||
_model_config: &ModelConfig,
|
||||
_session_id: &str,
|
||||
_system_prompt: &str,
|
||||
_messages: &[Message],
|
||||
_tools: &[Tool],
|
||||
) -> Result<(Message, ProviderUsage), ProviderError> {
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let tool_call = CallToolRequestParams {
|
||||
meta: None,
|
||||
task: None,
|
||||
@@ -405,21 +407,7 @@ mod tests {
|
||||
Usage::new(Some(10), Some(5), Some(15)),
|
||||
);
|
||||
|
||||
Ok((message, usage))
|
||||
}
|
||||
|
||||
async fn complete_with_model(
|
||||
&self,
|
||||
session_id: Option<&str>,
|
||||
_model_config: &ModelConfig,
|
||||
system_prompt: &str,
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
) -> anyhow::Result<(Message, ProviderUsage), ProviderError> {
|
||||
// Test-only: coerce missing session_id to empty so complete() can be reused.
|
||||
let session_id = session_id.unwrap_or("");
|
||||
self.complete(session_id, system_prompt, messages, tools)
|
||||
.await
|
||||
Ok(stream_from_single_message(message, usage))
|
||||
}
|
||||
|
||||
fn get_model_config(&self) -> ModelConfig {
|
||||
|
||||
@@ -5,7 +5,10 @@ use goose::agents::{Agent, AgentEvent, SessionConfig};
|
||||
use goose::conversation::message::{Message, MessageContent};
|
||||
use goose::conversation::Conversation;
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::base::{Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage};
|
||||
use goose::providers::base::{
|
||||
stream_from_single_message, MessageStream, Provider, ProviderDef, ProviderMetadata,
|
||||
ProviderUsage, Usage,
|
||||
};
|
||||
use goose::providers::errors::ProviderError;
|
||||
use goose::session::session_manager::SessionType;
|
||||
use goose::session::Session;
|
||||
@@ -94,14 +97,14 @@ impl MockCompactionProvider {
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for MockCompactionProvider {
|
||||
async fn complete_with_model(
|
||||
async fn stream(
|
||||
&self,
|
||||
_session_id: Option<&str>,
|
||||
_model_config: &ModelConfig,
|
||||
_session_id: &str,
|
||||
system_prompt: &str,
|
||||
messages: &[Message],
|
||||
_tools: &[Tool],
|
||||
) -> Result<(Message, ProviderUsage), ProviderError> {
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
// Check if this is a compaction call (message contains "summarize")
|
||||
let is_compaction = messages.iter().any(|msg| {
|
||||
msg.content.iter().any(|content| {
|
||||
@@ -163,7 +166,7 @@ impl Provider for MockCompactionProvider {
|
||||
),
|
||||
);
|
||||
|
||||
Ok((message, usage))
|
||||
Ok(stream_from_single_message(message, usage))
|
||||
}
|
||||
|
||||
fn get_model_config(&self) -> ModelConfig {
|
||||
|
||||
@@ -19,7 +19,10 @@ use test_case::test_case;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use goose::conversation::message::Message;
|
||||
use goose::providers::base::{Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage};
|
||||
use goose::providers::base::{
|
||||
stream_from_single_message, MessageStream, Provider, ProviderDef, ProviderMetadata,
|
||||
ProviderUsage, Usage,
|
||||
};
|
||||
use goose::providers::errors::ProviderError;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::process::Command;
|
||||
@@ -69,18 +72,17 @@ impl Provider for MockProvider {
|
||||
"mock"
|
||||
}
|
||||
|
||||
async fn complete_with_model(
|
||||
async fn stream(
|
||||
&self,
|
||||
_session_id: Option<&str>,
|
||||
_model_config: &ModelConfig,
|
||||
_session_id: &str,
|
||||
_system: &str,
|
||||
_messages: &[Message],
|
||||
_tools: &[Tool],
|
||||
) -> anyhow::Result<(Message, ProviderUsage), ProviderError> {
|
||||
Ok((
|
||||
Message::assistant().with_text("\"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"),
|
||||
ProviderUsage::new("mock".to_string(), Usage::default()),
|
||||
))
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let message = Message::assistant().with_text("\"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)");
|
||||
let usage = ProviderUsage::new("mock".to_string(), Usage::default());
|
||||
Ok(stream_from_single_message(message, usage))
|
||||
}
|
||||
|
||||
fn get_model_config(&self) -> ModelConfig {
|
||||
|
||||
@@ -127,9 +127,16 @@ impl ProviderTester {
|
||||
.build();
|
||||
|
||||
let message = Message::user().with_text(prompt);
|
||||
let model_config = self.provider.get_model_config();
|
||||
let (response1, _) = self
|
||||
.provider
|
||||
.complete(session_id, &system, std::slice::from_ref(&message), &tools)
|
||||
.complete(
|
||||
&model_config,
|
||||
session_id,
|
||||
&system,
|
||||
std::slice::from_ref(&message),
|
||||
&tools,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Agentic CLI providers (claude-code, codex) call tools internally and
|
||||
@@ -163,6 +170,7 @@ impl ProviderTester {
|
||||
let (response2, _) = self
|
||||
.provider
|
||||
.complete(
|
||||
&model_config,
|
||||
session_id,
|
||||
&system,
|
||||
&[message, response1, tool_response],
|
||||
@@ -174,10 +182,17 @@ impl ProviderTester {
|
||||
|
||||
async fn test_basic_response(&self, session_id: &str) -> Result<()> {
|
||||
let message = Message::user().with_text("Just say hello!");
|
||||
let model_config = self.provider.get_model_config();
|
||||
|
||||
let (response, _) = self
|
||||
.provider
|
||||
.complete(session_id, "You are a helpful assistant.", &[message], &[])
|
||||
.complete(
|
||||
&model_config,
|
||||
session_id,
|
||||
"You are a helpful assistant.",
|
||||
&[message],
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
@@ -227,10 +242,17 @@ impl ProviderTester {
|
||||
};
|
||||
|
||||
let messages = vec![Message::user().with_text(&large_message_content)];
|
||||
let model_config = self.provider.get_model_config();
|
||||
|
||||
let result = self
|
||||
.provider
|
||||
.complete(session_id, "You are a helpful assistant.", &messages, &[])
|
||||
.complete(
|
||||
&model_config,
|
||||
session_id,
|
||||
"You are a helpful assistant.",
|
||||
&messages,
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("=== {}::context_length_exceeded_error ===", self.name);
|
||||
@@ -288,9 +310,9 @@ impl ProviderTester {
|
||||
let message = Message::user().with_text("Just say hello!");
|
||||
let (response, _) = self
|
||||
.provider
|
||||
.complete_with_model(
|
||||
Some(session_id),
|
||||
.complete(
|
||||
&alt_config,
|
||||
session_id,
|
||||
"You are a helpful assistant.",
|
||||
&[message],
|
||||
&[],
|
||||
|
||||
@@ -54,24 +54,33 @@ async fn setup_mock_server() -> (MockServer, HeaderCapture, Box<dyn Provider>) {
|
||||
.and(path("/v1/chat/completions"))
|
||||
.respond_with(move |req: &Request| {
|
||||
capture_clone.capture_session_header(req);
|
||||
ResponseTemplate::new(200).set_body_json(json!({
|
||||
"choices": [{
|
||||
"finish_reason": "stop",
|
||||
"index": 0,
|
||||
"message": {
|
||||
"content": "Hi there! How can I help you today?",
|
||||
"role": "assistant"
|
||||
// Return SSE streaming format
|
||||
let sse_response = format!(
|
||||
"data: {}\n\ndata: {}\n\ndata: [DONE]\n\n",
|
||||
json!({
|
||||
"choices": [{
|
||||
"delta": {
|
||||
"content": "Hi there! How can I help you today?",
|
||||
"role": "assistant"
|
||||
},
|
||||
"index": 0
|
||||
}],
|
||||
"created": 1755133833,
|
||||
"id": "chatcmpl-test",
|
||||
"model": "gpt-5-nano"
|
||||
}),
|
||||
json!({
|
||||
"choices": [],
|
||||
"usage": {
|
||||
"completion_tokens": 10,
|
||||
"prompt_tokens": 8,
|
||||
"total_tokens": 18
|
||||
}
|
||||
}],
|
||||
"created": 1755133833,
|
||||
"id": "chatcmpl-test",
|
||||
"model": "gpt-5-nano",
|
||||
"usage": {
|
||||
"completion_tokens": 10,
|
||||
"prompt_tokens": 8,
|
||||
"total_tokens": 18
|
||||
}
|
||||
}))
|
||||
})
|
||||
);
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(sse_response)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
})
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
@@ -82,8 +91,15 @@ async fn setup_mock_server() -> (MockServer, HeaderCapture, Box<dyn Provider>) {
|
||||
|
||||
async fn make_request(provider: &dyn Provider, session_id: &str) {
|
||||
let message = Message::user().with_text("test message");
|
||||
let model_config = provider.get_model_config();
|
||||
let _ = provider
|
||||
.complete(session_id, "You are a helpful assistant.", &[message], &[])
|
||||
.complete(
|
||||
&model_config,
|
||||
session_id,
|
||||
"You are a helpful assistant.",
|
||||
&[message],
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@ mod tetrate_streaming_tests {
|
||||
let provider = create_test_provider().await?;
|
||||
|
||||
let messages = vec![Message::user().with_text("Count from 1 to 5, one number at a time.")];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let mut stream = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant that counts numbers.",
|
||||
&messages,
|
||||
@@ -99,9 +101,11 @@ mod tetrate_streaming_tests {
|
||||
);
|
||||
|
||||
let messages = vec![Message::user().with_text("What's the weather in San Francisco?")];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let mut stream = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant with access to weather information.",
|
||||
&messages,
|
||||
@@ -147,9 +151,11 @@ mod tetrate_streaming_tests {
|
||||
|
||||
// This might result in a very short or empty response
|
||||
let messages = vec![Message::user().with_text("")];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let mut stream = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant.",
|
||||
&messages,
|
||||
@@ -182,9 +188,11 @@ mod tetrate_streaming_tests {
|
||||
let messages = vec![Message::user().with_text(
|
||||
"Write a detailed 3-paragraph essay about the importance of streaming in modern APIs.",
|
||||
)];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let mut stream = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant that writes detailed essays.",
|
||||
&messages,
|
||||
@@ -243,9 +251,11 @@ mod tetrate_streaming_tests {
|
||||
let provider = TetrateProvider::from_env(model_config).await?;
|
||||
|
||||
let messages = vec![Message::user().with_text("Hello")];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let result = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant.",
|
||||
&messages,
|
||||
@@ -271,9 +281,11 @@ mod tetrate_streaming_tests {
|
||||
// Create multiple concurrent streams
|
||||
let messages1 = vec![Message::user().with_text("Say 'Stream 1'")];
|
||||
let messages2 = vec![Message::user().with_text("Say 'Stream 2'")];
|
||||
let model_config = provider.get_model_config();
|
||||
|
||||
let stream1 = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant.",
|
||||
&messages1,
|
||||
@@ -283,6 +295,7 @@ mod tetrate_streaming_tests {
|
||||
|
||||
let stream2 = provider
|
||||
.stream(
|
||||
&model_config,
|
||||
"test-session-id",
|
||||
"You are a helpful assistant.",
|
||||
&messages2,
|
||||
|
||||
Reference in New Issue
Block a user