mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
refactor the request logger out of providers (#9847)
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
This commit is contained in:
parent
08fb6d45f3
commit
0f1846a439
31 changed files with 286 additions and 172 deletions
|
|
@ -1,53 +1,31 @@
|
|||
use anyhow::Result;
|
||||
use std::sync::Once;
|
||||
use goose::providers::utils::init_goose_request_log;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// Used to ensure we only set up tracing once
|
||||
static INIT: Once = Once::new();
|
||||
static INIT: OnceLock<Result<()>> = OnceLock::new();
|
||||
|
||||
/// Sets up the logging infrastructure for the CLI.
|
||||
/// Logs go to a JSON file only (no console output).
|
||||
pub fn setup_logging(name: Option<&str>) -> Result<()> {
|
||||
setup_logging_internal(name, false)
|
||||
}
|
||||
pub fn setup_logging(name: Option<&str>) -> &'static Result<()> {
|
||||
INIT.get_or_init(|| {
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
fn setup_logging_internal(name: Option<&str>, force: bool) -> Result<()> {
|
||||
let mut result = Ok(());
|
||||
init_goose_request_log()?;
|
||||
let config = goose::logging::LoggingConfig {
|
||||
component: "cli",
|
||||
name,
|
||||
extra_directives: &["goose_cli=info"],
|
||||
console: false,
|
||||
json: true,
|
||||
};
|
||||
let subscriber = goose::logging::build_logging_subscriber(&config)?;
|
||||
|
||||
let mut setup = || {
|
||||
result = (|| {
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
let config = goose::logging::LoggingConfig {
|
||||
component: "cli",
|
||||
name,
|
||||
extra_directives: &["goose_cli=info"],
|
||||
console: false,
|
||||
json: true,
|
||||
};
|
||||
let subscriber = goose::logging::build_logging_subscriber(&config)?;
|
||||
|
||||
if force {
|
||||
let _guard = subscriber.set_default();
|
||||
tracing::warn!("Test log entry from setup");
|
||||
tracing::info!("Another test log entry from setup");
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
Ok(())
|
||||
} else {
|
||||
subscriber
|
||||
.try_init()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
if force {
|
||||
setup();
|
||||
} else {
|
||||
INIT.call_once(setup);
|
||||
}
|
||||
|
||||
result
|
||||
subscriber
|
||||
.try_init()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {}", e))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -168,6 +168,11 @@ pub async fn collect_stream(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn stream_from_single_message(message: Message, usage: ProviderUsage) -> MessageStream {
|
||||
let stream = futures::stream::once(async move { Ok((Some(message), Some(usage))) });
|
||||
Box::pin(stream)
|
||||
}
|
||||
|
||||
/// Base trait for AI providers (OpenAI, Anthropic, etc)
|
||||
#[async_trait]
|
||||
pub trait Provider: Send + Sync {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use reqwest::StatusCode;
|
|||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::request_log::LogError;
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq)]
|
||||
pub enum ProviderError {
|
||||
#[error("Authentication error: {0}")]
|
||||
|
|
@ -142,6 +144,12 @@ impl From<reqwest::Error> for ProviderError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<LogError> for ProviderError {
|
||||
fn from(value: LogError) -> Self {
|
||||
ProviderError::ExecutionError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GoogleErrorCode {
|
||||
BadRequest = 400,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub mod json;
|
|||
pub(crate) mod mcp_utils;
|
||||
pub mod model;
|
||||
pub mod permission;
|
||||
pub mod request_log;
|
||||
pub mod retry;
|
||||
pub mod thinking;
|
||||
pub mod utils;
|
||||
|
|
|
|||
134
crates/goose-providers/src/request_log.rs
Normal file
134
crates/goose-providers/src/request_log.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::conversation::token_usage::Usage;
|
||||
|
||||
type RequestLogError = Box<dyn Error + Send + Sync>;
|
||||
|
||||
static LOGGER: OnceLock<Arc<dyn RequestLogger>> = OnceLock::new();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoggerAlreadyInstalled;
|
||||
|
||||
impl Display for LoggerAlreadyInstalled {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "request logger is already installed")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for LoggerAlreadyInstalled {}
|
||||
|
||||
pub fn install_logger<R: RequestLogger + 'static>(r: R) -> Result<(), LoggerAlreadyInstalled> {
|
||||
LOGGER.set(Arc::new(r)).map_err(|_| LoggerAlreadyInstalled)
|
||||
}
|
||||
|
||||
pub trait RequestLogger: Send + Sync {
|
||||
fn start(&self) -> Result<Box<dyn RequestLogHandle>, RequestLogError>;
|
||||
}
|
||||
|
||||
pub trait RequestLogHandle: Send {
|
||||
fn write(&mut self, s: &str) -> Result<(), RequestLogError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogError {
|
||||
LoggerError(String),
|
||||
SerializeError(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Display for LogError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LogError::LoggerError(msg) => write!(f, "{}", msg),
|
||||
LogError::SerializeError(error) => write!(f, "serialize error: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for LogError {}
|
||||
|
||||
impl From<RequestLogError> for LogError {
|
||||
fn from(value: RequestLogError) -> Self {
|
||||
Self::LoggerError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(v: &serde_json::Value) -> Result<String, LogError> {
|
||||
serde_json::to_string(v).map_err(LogError::SerializeError)
|
||||
}
|
||||
|
||||
pub fn start_log<M, P>(
|
||||
model_config: M,
|
||||
payload: P,
|
||||
) -> Result<Option<Box<dyn RequestLogHandle>>, LogError>
|
||||
where
|
||||
M: Serialize,
|
||||
P: Serialize,
|
||||
{
|
||||
let logger = if let Some(logger) = LOGGER.get() {
|
||||
logger
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut handle = logger.start()?;
|
||||
let payload = json!({
|
||||
"model_config": model_config,
|
||||
"input": payload,
|
||||
});
|
||||
|
||||
handle.write(serialize(&payload)?.as_str())?;
|
||||
Ok(Some(handle))
|
||||
}
|
||||
|
||||
pub trait LoggerHandleExt {
|
||||
fn write<Payload>(&mut self, data: &Payload, usage: Option<&Usage>) -> Result<(), LogError>
|
||||
where
|
||||
Payload: Serialize;
|
||||
fn error<E>(&mut self, error: E) -> Result<(), LogError>
|
||||
where
|
||||
E: Display;
|
||||
}
|
||||
|
||||
impl LoggerHandleExt for Option<Box<dyn RequestLogHandle>> {
|
||||
fn write<Payload>(&mut self, data: &Payload, usage: Option<&Usage>) -> Result<(), LogError>
|
||||
where
|
||||
Payload: Serialize,
|
||||
{
|
||||
let log = if let Some(log) = self {
|
||||
log
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let line = serialize(&json!({
|
||||
"data": data,
|
||||
"usage": usage,
|
||||
}))?;
|
||||
|
||||
Ok(log.write(line.as_str())?)
|
||||
}
|
||||
|
||||
fn error<E>(&mut self, error: E) -> Result<(), LogError>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
let log = if let Some(log) = self {
|
||||
log
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let line = serialize(&json!({
|
||||
"error": format!("{}", error),
|
||||
}))?;
|
||||
|
||||
Ok(log.write(line.as_str())?)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use goose::providers::utils::init_goose_request_log;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
/// Sets up the logging infrastructure for the server.
|
||||
/// Logs go to a JSON file and a pretty console layer on stderr.
|
||||
pub fn setup_logging(name: Option<&str>) -> Result<()> {
|
||||
init_goose_request_log()?;
|
||||
let config = goose::logging::LoggingConfig {
|
||||
component: "server",
|
||||
name,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use async_stream::try_stream;
|
|||
use async_trait::async_trait;
|
||||
use futures::TryStreamExt;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::Value;
|
||||
use std::io;
|
||||
|
|
@ -20,7 +21,6 @@ use super::openai_compatible::map_http_error_to_provider_error;
|
|||
use super::retry::ProviderRetry;
|
||||
use crate::config::declarative_providers::DeclarativeProviderConfig;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use futures::future::BoxFuture;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use rmcp::model::Tool;
|
||||
|
|
@ -330,7 +330,7 @@ impl Provider for AnthropicProvider {
|
|||
.insert("stream".to_string(), Value::Bool(true));
|
||||
|
||||
let conditional_headers = self.get_conditional_headers();
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -362,6 +362,7 @@ impl Provider for AnthropicProvider {
|
|||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ pub const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600;
|
|||
|
||||
use crate::config::base::ConfigValue;
|
||||
use crate::config::ExtensionConfig;
|
||||
use goose_providers::conversation::message::Message;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
|
|
@ -264,11 +263,6 @@ pub trait ProviderDef: Send + Sync {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn stream_from_single_message(message: Message, usage: ProviderUsage) -> MessageStream {
|
||||
let stream = futures::stream::once(async move { Ok((Some(message), Some(usage))) });
|
||||
Box::pin(stream)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use super::formats::openai_responses::create_responses_request;
|
|||
use super::openai_compatible::{handle_status, stream_responses_compat};
|
||||
use super::retry::{ProviderRetry, RetryConfig};
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use crate::session_context::SESSION_ID_HEADER;
|
||||
use anyhow::Result;
|
||||
use async_stream::try_stream;
|
||||
|
|
@ -21,6 +20,7 @@ use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
|||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::formats::openai::extract_reasoning_effort;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use reqwest::header::{HeaderName, HeaderValue, AUTHORIZATION};
|
||||
use rmcp::model::{object, CallToolRequestParams, ErrorCode, ErrorData, Tool};
|
||||
use serde_json::Value;
|
||||
|
|
@ -526,7 +526,7 @@ impl BedrockProvider {
|
|||
"messages": messages,
|
||||
"tools": tools
|
||||
});
|
||||
let mut log = RequestLog::start(&self.model, &debug_payload)?;
|
||||
let mut log = start_log(&self.model, &debug_payload)?;
|
||||
log.write(
|
||||
&serde_json::to_value(&message).unwrap_or_default(),
|
||||
Some(&usage),
|
||||
|
|
@ -775,7 +775,7 @@ impl Provider for BedrockProvider {
|
|||
create_responses_request(&normalized_config, system, messages, tools)?;
|
||||
payload["model"] = Value::String(bedrock_model_id.clone());
|
||||
payload["stream"] = Value::Bool(true);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload).map_err(anyhow::Error::from)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| self.post_mantle_streaming(session_id_opt, &payload))
|
||||
|
|
@ -810,7 +810,7 @@ impl Provider for BedrockProvider {
|
|||
"messages": messages,
|
||||
"tools": tools
|
||||
});
|
||||
let mut log = RequestLog::start(&self.model, &debug_payload)?;
|
||||
let mut log = start_log(&self.model, &debug_payload)?;
|
||||
|
||||
let mut event_stream = response.stream;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use tokio::io::{AsyncBufReadExt, BufReader};
|
|||
use tokio::process::Command;
|
||||
|
||||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::utils::{filter_extensions_from_system_prompt, RequestLog};
|
||||
use super::utils::filter_extensions_from_system_prompt;
|
||||
use crate::config::base::{CodexCommand, CodexSkipGitCheck};
|
||||
use crate::config::paths::Paths;
|
||||
use crate::config::search_path::SearchPaths;
|
||||
|
|
@ -23,6 +23,7 @@ use crate::conversation::message::{Message, MessageContent};
|
|||
use crate::subprocess::configure_subprocess;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Role;
|
||||
use rmcp::model::Tool;
|
||||
|
||||
|
|
@ -717,9 +718,7 @@ impl Provider for CodexProvider {
|
|||
"messages_count": messages.len()
|
||||
});
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload).map_err(|e| {
|
||||
ProviderError::RequestFailed(format!("Failed to start request log: {}", e))
|
||||
})?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = json!({
|
||||
"lines": lines.len(),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use tokio::process::Command;
|
|||
use super::base::{
|
||||
stream_from_single_message, ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata,
|
||||
};
|
||||
use super::utils::{filter_extensions_from_system_prompt, RequestLog};
|
||||
use super::utils::filter_extensions_from_system_prompt;
|
||||
use crate::config::base::CursorAgentCommand;
|
||||
use crate::config::search_path::SearchPaths;
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
|
|
@ -19,6 +19,7 @@ use futures::future::BoxFuture;
|
|||
use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const CURSOR_AGENT_PROVIDER_NAME: &str = "cursor-agent";
|
||||
|
|
@ -352,7 +353,7 @@ impl Provider for CursorAgentProvider {
|
|||
"usage": usage
|
||||
});
|
||||
|
||||
let mut log = RequestLog::start(&self.model, &payload)?;
|
||||
let mut log = start_log(&self.model, &payload)?;
|
||||
log.write(&response, Some(&usage))?;
|
||||
|
||||
let provider_usage = ProviderUsage::new(model_config.model_name.clone(), usage);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ use super::openai_compatible::{
|
|||
stream_responses_compat,
|
||||
};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::config::ConfigError;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::instance_id::get_instance_id;
|
||||
|
|
@ -34,6 +33,7 @@ use crate::providers::retry::{
|
|||
};
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
use serde_json::json;
|
||||
|
||||
|
|
@ -653,7 +653,7 @@ impl Provider for DatabricksProvider {
|
|||
payload["client_request_id"] = Value::String(client_request_id.clone());
|
||||
}
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -718,7 +718,7 @@ impl Provider for DatabricksProvider {
|
|||
.insert("stream_options".to_string(), json!({"include_usage": true}));
|
||||
}
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
let resp = self
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ use super::databricks_auth::{DatabricksAuth, DatabricksAuthProvider};
|
|||
use super::formats::{anthropic, openai_responses};
|
||||
use super::openai_compatible::{handle_status, stream_openai_compat, stream_responses_compat};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::config::ConfigError;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::retry::{
|
||||
|
|
@ -31,6 +30,7 @@ use crate::providers::retry::{
|
|||
};
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const DATABRICKS_V2_PROVIDER_NAME: &str = "databricks_v2";
|
||||
|
|
@ -231,7 +231,7 @@ impl DatabricksV2Provider {
|
|||
let mut payload =
|
||||
openai_responses::create_responses_request(model_config, system, messages, tools)?;
|
||||
payload["stream"] = Value::Bool(true);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -268,7 +268,7 @@ impl DatabricksV2Provider {
|
|||
if payload.get("max_tokens").is_none() {
|
||||
payload["max_tokens"] = Value::from(model_config.max_output_tokens());
|
||||
}
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -300,7 +300,7 @@ impl DatabricksV2Provider {
|
|||
) -> Result<MessageStream, ProviderError> {
|
||||
let mut payload = anthropic::create_request(model_config, system, messages, tools)?;
|
||||
payload["stream"] = Value::Bool(true);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ use crate::providers::formats::gcpvertexai::{
|
|||
use crate::providers::gcpauth::GcpAuth;
|
||||
use crate::providers::openai_compatible::{map_http_error_to_provider_error, sanitize_url};
|
||||
use crate::providers::retry::RetryConfig;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use crate::session_context::SESSION_ID_HEADER;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const GCP_VERTEX_AI_PROVIDER_NAME: &str = "gcp_vertex_ai";
|
||||
|
|
@ -618,7 +618,7 @@ impl Provider for GcpVertexAIProvider {
|
|||
}
|
||||
}
|
||||
|
||||
let mut log = RequestLog::start(model_config, &request)?;
|
||||
let mut log = start_log(model_config, &request)?;
|
||||
|
||||
let response = self
|
||||
.post_stream(Some(session_id), &request, &context)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ use crate::providers::formats::google::{create_request, response_to_streaming_me
|
|||
use crate::providers::google::GOOGLE_DOC_URL;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
|
||||
const GEMINI_OAUTH_DEFAULT_MODEL: &str = "gemini-3-flash-preview";
|
||||
const GEMINI_OAUTH_DEFAULT_FAST_MODEL: &str = "gemini-2.5-flash-lite";
|
||||
use crate::providers::retry::ProviderRetry;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use crate::session_context::SESSION_ID_HEADER;
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_stream::try_stream;
|
||||
|
|
@ -995,7 +995,7 @@ impl Provider for GeminiOAuthProvider {
|
|||
tools: &[Tool],
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let payload = create_request(model_config, system, messages, tools)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ use super::base::{
|
|||
use super::formats::openai_responses::create_responses_request;
|
||||
use super::openai_compatible::handle_response_openai_compat;
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::{get_model, RequestLog};
|
||||
use super::utils::get_model;
|
||||
use goose_providers::formats::openai::{create_request, get_usage, response_to_message};
|
||||
|
||||
use crate::config::{Config, ConfigError};
|
||||
|
|
@ -40,6 +40,7 @@ use crate::providers::base::{ConfigKey, MessageStream};
|
|||
use futures::future::BoxFuture;
|
||||
use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::{RawContent, Tool};
|
||||
use std::ops::Deref;
|
||||
|
||||
|
|
@ -403,7 +404,7 @@ impl GithubCopilotProvider {
|
|||
.map_err(|e| ProviderError::RequestFailed(e.to_string()))?;
|
||||
payload["stream"] = serde_json::Value::Bool(true);
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -451,7 +452,7 @@ impl GithubCopilotProvider {
|
|||
&ImageFormat::OpenAi,
|
||||
true,
|
||||
)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -487,7 +488,7 @@ impl GithubCopilotProvider {
|
|||
&ImageFormat::OpenAi,
|
||||
false,
|
||||
)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use super::api_client::{ApiClient, AuthMethod};
|
|||
use super::base::MessageStream;
|
||||
use super::openai_compatible::{handle_status, map_http_error_to_provider_error, sanitize_url};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::Message;
|
||||
use goose_providers::errors::ProviderError;
|
||||
|
||||
|
|
@ -14,6 +13,7 @@ use async_trait::async_trait;
|
|||
use futures::future::BoxFuture;
|
||||
use futures::TryStreamExt;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
use serde_json::Value;
|
||||
use std::io;
|
||||
|
|
@ -192,7 +192,7 @@ impl Provider for GoogleProvider {
|
|||
tools: &[Tool],
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let payload = create_request(model_config, system, messages, tools)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ use super::oauth_device_flow::{
|
|||
};
|
||||
use super::openai_compatible::handle_status;
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::Message;
|
||||
use futures::future::BoxFuture;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const KIMI_CODE_PROVIDER_NAME: &str = "kimi_code";
|
||||
|
|
@ -404,7 +404,7 @@ impl Provider for KimiCodeProvider {
|
|||
.unwrap()
|
||||
.insert("stream".to_string(), Value::Bool(true));
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)
|
||||
let mut log = start_log(model_config, &payload)
|
||||
.map_err(|e| ProviderError::RequestFailed(e.to_string()))?;
|
||||
|
||||
let response = self
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ use super::base::{
|
|||
};
|
||||
use super::openai_compatible::handle_response_openai_compat;
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::{get_model, RequestLog};
|
||||
use super::utils::get_model;
|
||||
use crate::conversation::message::Message;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const LITELLM_PROVIDER_NAME: &str = "litellm";
|
||||
|
|
@ -249,7 +250,7 @@ impl Provider for LiteLLMProvider {
|
|||
let message = goose_providers::formats::openai::response_to_message(&response)?;
|
||||
let usage = goose_providers::formats::openai::get_usage(&response);
|
||||
let response_model = get_model(&response);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
log.write(&response, Some(&usage))?;
|
||||
let provider_usage = ProviderUsage::new(response_model, usage);
|
||||
Ok(super::base::stream_from_single_message(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ mod tool_parsing;
|
|||
use crate::config::ExtensionConfig;
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
use crate::providers::base::{MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use crate::providers::utils::RequestLog;
|
||||
use anyhow::Result;
|
||||
use async_stream::try_stream;
|
||||
use async_trait::async_trait;
|
||||
|
|
@ -23,6 +22,7 @@ use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
|||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::images::ImageFormat;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt, RequestLogHandle};
|
||||
use llamacpp::{LlamaCppBackend, LLAMACPP_BACKEND_ID};
|
||||
use local_model_registry::ChatTemplate;
|
||||
use mlx::{MlxBackend, MLX_BACKEND_ID};
|
||||
|
|
@ -447,7 +447,7 @@ fn strip_info_messages(text: &str) -> String {
|
|||
|
||||
/// Build a `ProviderUsage` and write the request log entry.
|
||||
fn finalize_usage(
|
||||
log: &mut RequestLog,
|
||||
log: &mut Option<Box<dyn RequestLogHandle>>,
|
||||
model_name: String,
|
||||
path_label: &str,
|
||||
prompt_token_count: usize,
|
||||
|
|
@ -651,8 +651,7 @@ impl Provider for LocalInferenceProvider {
|
|||
},
|
||||
});
|
||||
|
||||
let mut log = RequestLog::start(&self.model_config, &log_payload)
|
||||
.map_err(|e| ProviderError::ExecutionError(e.to_string()))?;
|
||||
let mut log = start_log(&self.model_config, &log_payload)?;
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel::<
|
||||
Result<(Option<Message>, Option<ProviderUsage>), ProviderError>,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::any::Any;
|
|||
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::local_inference::local_model_registry::ModelSettings;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::request_log::RequestLogHandle;
|
||||
|
||||
use super::{ResolvedModelPaths, StreamSender};
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ pub(super) struct LocalGenerationRequest<'a> {
|
|||
pub draft_model_path: Option<std::path::PathBuf>,
|
||||
pub message_id: &'a str,
|
||||
pub tx: &'a StreamSender,
|
||||
pub log: &'a mut RequestLog,
|
||||
pub log: &'a mut Option<Box<dyn RequestLogHandle>>,
|
||||
}
|
||||
|
||||
pub(super) trait LocalInferenceBackend: Send + Sync {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::providers::local_inference::backend::LocalInferenceBackend;
|
||||
use crate::providers::local_inference::local_model_registry::ModelSettings;
|
||||
use crate::providers::local_inference::multimodal::ExtractedImage;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::request_log::{LoggerHandleExt, RequestLogHandle};
|
||||
use goose_providers::thinking::{FilterOut, ThinkFilter};
|
||||
use llama_cpp_2::context::params::LlamaContextParams;
|
||||
use llama_cpp_2::llama_batch::LlamaBatch;
|
||||
|
|
@ -24,7 +24,7 @@ pub(super) struct GenerationContext<'a> {
|
|||
pub model_name: String,
|
||||
pub message_id: &'a str,
|
||||
pub tx: &'a StreamSender,
|
||||
pub log: &'a mut RequestLog,
|
||||
pub log: &'a mut Option<Box<dyn RequestLogHandle>>,
|
||||
pub images: &'a [ExtractedImage],
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use super::api_client::{ApiClient, AuthMethod};
|
|||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::openai_compatible::{handle_status, stream_openai_compat};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::Message;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
|
@ -11,6 +10,7 @@ use goose_providers::errors::ProviderError;
|
|||
use goose_providers::formats::openai::create_request;
|
||||
use goose_providers::images::ImageFormat;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
pub const NANOGPT_PROVIDER_NAME: &str = "nano-gpt";
|
||||
|
|
@ -184,7 +184,7 @@ impl Provider for NanoGptProvider {
|
|||
true,
|
||||
)?;
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use super::base::{
|
|||
};
|
||||
use super::openai_compatible::handle_status;
|
||||
use super::retry::{ProviderRetry, RetryConfig};
|
||||
use super::utils::RequestLog;
|
||||
use crate::config::declarative_providers::DeclarativeProviderConfig;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::formats::ollama::{create_request, response_to_streaming_message_ollama};
|
||||
|
|
@ -17,6 +16,7 @@ use futures::TryStreamExt;
|
|||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::images::ImageFormat;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt, RequestLogHandle};
|
||||
use reqwest::Response;
|
||||
use rmcp::model::Tool;
|
||||
use serde_json::{json, Value};
|
||||
|
|
@ -307,7 +307,7 @@ impl Provider for OllamaProvider {
|
|||
true,
|
||||
)?;
|
||||
apply_ollama_options(&mut payload, model_config);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -431,7 +431,10 @@ fn with_line_timeout(
|
|||
/// preventing duplicate content from being emitted to the UI.
|
||||
/// Timeout is applied at the raw SSE line level via with_line_timeout so that
|
||||
/// buffering inside response_to_streaming_message_ollama does not cause false stalls.
|
||||
fn stream_ollama(response: Response, mut log: RequestLog) -> Result<MessageStream, ProviderError> {
|
||||
fn stream_ollama(
|
||||
response: Response,
|
||||
mut log: Option<Box<dyn RequestLogHandle>>,
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let stream = response.bytes_stream().map_err(std::io::Error::other);
|
||||
|
||||
Ok(Box::pin(try_stream! {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ use goose_providers::formats::openai::{
|
|||
create_request_with_options, get_usage, response_to_message, OpenAiFormatOptions,
|
||||
};
|
||||
use goose_providers::images::ImageFormat;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use reqwest::StatusCode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::providers::base::MessageStream;
|
||||
use crate::providers::utils::RequestLog;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use rmcp::model::Tool;
|
||||
|
||||
|
|
@ -823,7 +823,7 @@ impl Provider for OpenAiProvider {
|
|||
let mut payload = create_responses_request(model_config, system, messages, tools)?;
|
||||
payload["stream"] = serde_json::Value::Bool(self.supports_streaming);
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
@ -886,7 +886,7 @@ impl Provider for OpenAiProvider {
|
|||
},
|
||||
)?;
|
||||
let payload = self.sanitize_request_for_compat(payload);
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use tokio_util::io::StreamReader;
|
|||
use super::api_client::ApiClient;
|
||||
use super::base::{stream_from_single_message, MessageStream, Provider};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::formats::openai_responses::responses_api_to_streaming_message;
|
||||
use goose_providers::errors::ProviderError;
|
||||
|
|
@ -23,6 +22,7 @@ use goose_providers::formats::openai::{
|
|||
create_request, get_usage, response_to_message, response_to_streaming_message,
|
||||
};
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt, RequestLogHandle};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
pub struct OpenAiCompatibleProvider {
|
||||
|
|
@ -128,7 +128,7 @@ impl Provider for OpenAiCompatibleProvider {
|
|||
tools,
|
||||
self.supports_streaming,
|
||||
)?;
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let completions_path = format!("{}chat/completions", self.completions_prefix);
|
||||
let response = self
|
||||
|
|
@ -179,7 +179,7 @@ pub use super::http_status::handle_response as handle_response_openai_compat;
|
|||
|
||||
pub fn stream_openai_compat(
|
||||
response: Response,
|
||||
mut log: RequestLog,
|
||||
mut log: Option<Box<dyn RequestLogHandle>>,
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let stream = response.bytes_stream().map_err(std::io::Error::other);
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ pub fn stream_openai_compat(
|
|||
|
||||
pub fn stream_responses_compat(
|
||||
response: Response,
|
||||
mut log: RequestLog,
|
||||
mut log: Option<Box<dyn RequestLogHandle>>,
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let stream = response.bytes_stream().map_err(std::io::Error::other);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ use super::api_client::{ApiClient, AuthMethod};
|
|||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::openai_compatible::{handle_status, stream_openai_compat};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::formats::openrouter as openrouter_format;
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::formats::openai::create_request;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
pub const OPENROUTER_PROVIDER_NAME: &str = "openrouter";
|
||||
|
|
@ -289,7 +289,7 @@ impl Provider for OpenRouterProvider {
|
|||
obj.insert("transforms".to_string(), json!(["middle-out"]));
|
||||
}
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ use smithy_transport_reqwest::ReqwestHttpClient;
|
|||
|
||||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
use crate::session_context::SESSION_ID_HEADER;
|
||||
use goose_providers::errors::ProviderError;
|
||||
|
|
@ -21,6 +20,7 @@ use chrono::Utc;
|
|||
use futures::future::BoxFuture;
|
||||
use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Role;
|
||||
|
||||
const SAGEMAKER_TGI_PROVIDER_NAME: &str = "sagemaker_tgi";
|
||||
|
|
@ -350,7 +350,7 @@ impl Provider for SageMakerTgiProvider {
|
|||
"messages": messages,
|
||||
"tools": tools
|
||||
});
|
||||
let mut log = RequestLog::start(&self.model, &debug_payload)?;
|
||||
let mut log = start_log(&self.model, &debug_payload)?;
|
||||
log.write(
|
||||
&serde_json::to_value(&message).unwrap_or_default(),
|
||||
Some(&usage),
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetad
|
|||
use super::formats::snowflake::{create_request, get_usage, response_to_message};
|
||||
use super::openai_compatible::{map_http_error_to_provider_error, sanitize_url};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::{get_model, RequestLog};
|
||||
use super::utils::get_model;
|
||||
use crate::config::ConfigError;
|
||||
use crate::conversation::message::Message;
|
||||
use goose_providers::errors::ProviderError;
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const SNOWFLAKE_PROVIDER_NAME: &str = "snowflake";
|
||||
|
|
@ -356,7 +357,7 @@ impl Provider for SnowflakeProvider {
|
|||
};
|
||||
let payload = create_request(model_config, system, messages, tools)?;
|
||||
|
||||
let mut log = RequestLog::start(&self.model, &payload)?;
|
||||
let mut log = start_log(&self.model, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use super::openai_compatible::{
|
|||
stream_openai_compat,
|
||||
};
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::RequestLog;
|
||||
use crate::config::signup_tetrate::TETRATE_DEFAULT_MODEL;
|
||||
use crate::conversation::message::Message;
|
||||
use anyhow::Result;
|
||||
|
|
@ -16,6 +15,7 @@ use goose_providers::images::ImageFormat;
|
|||
|
||||
use goose_providers::formats::openai::create_request;
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::request_log::{start_log, LoggerHandleExt};
|
||||
use rmcp::model::Tool;
|
||||
use serde_json::Value;
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ impl Provider for TetrateProvider {
|
|||
true,
|
||||
)?;
|
||||
|
||||
let mut log = RequestLog::start(model_config, &payload)?;
|
||||
let mut log = start_log(model_config, &payload)?;
|
||||
|
||||
let response = self
|
||||
.with_retry(|| async {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::config::paths::Paths;
|
||||
use anyhow::{anyhow, Result};
|
||||
use fs_err::File;
|
||||
use goose_providers::conversation::token_usage::Usage;
|
||||
use goose_providers::errors::{GoogleErrorCode, ProviderError};
|
||||
use goose_providers::request_log::{install_logger, RequestLogHandle, RequestLogger};
|
||||
use reqwest::{Response, StatusCode};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::fmt::Display;
|
||||
use std::error::Error;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -212,19 +212,38 @@ fn unescape_json_values_in_place(value: &mut Value) {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RequestLog {
|
||||
writer: Option<BufWriter<File>>,
|
||||
temp_path: PathBuf,
|
||||
}
|
||||
|
||||
pub const LOGS_TO_KEEP: usize = 10;
|
||||
|
||||
static INIT_LOGGER: OnceLock<Result<()>> = OnceLock::new();
|
||||
|
||||
pub fn init_goose_request_log() -> Result<()> {
|
||||
INIT_LOGGER
|
||||
.get_or_init(|| Ok(install_logger(RequestLog::new(LOGS_TO_KEEP)?)?))
|
||||
.as_ref()
|
||||
.map_err(|e| anyhow::anyhow!("failed to set up logger: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct RequestLog {
|
||||
logs_to_keep: usize,
|
||||
}
|
||||
|
||||
impl RequestLog {
|
||||
pub fn start<ModelConfig, Payload>(model_config: ModelConfig, payload: &Payload) -> Result<Self>
|
||||
where
|
||||
ModelConfig: Serialize,
|
||||
Payload: Serialize,
|
||||
{
|
||||
pub fn new(logs_to_keep: usize) -> Result<Self> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
fs_err::create_dir_all(&logs_dir)?;
|
||||
Ok(Self { logs_to_keep })
|
||||
}
|
||||
}
|
||||
|
||||
struct FileLogHandle {
|
||||
writer: Option<BufWriter<File>>,
|
||||
temp_path: PathBuf,
|
||||
logs_to_keep: usize,
|
||||
}
|
||||
|
||||
impl RequestLogger for RequestLog {
|
||||
fn start(&self) -> Result<Box<dyn RequestLogHandle>, Box<dyn Error + Send + Sync>> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
fs_err::create_dir_all(&logs_dir)?;
|
||||
|
||||
|
|
@ -232,7 +251,7 @@ impl RequestLog {
|
|||
let temp_name = format!("llm_request.{request_id}.jsonl");
|
||||
let temp_path = logs_dir.join(PathBuf::from(temp_name));
|
||||
|
||||
let mut writer = BufWriter::new(
|
||||
let writer = BufWriter::new(
|
||||
File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
|
|
@ -240,53 +259,38 @@ impl RequestLog {
|
|||
.open(&temp_path)?,
|
||||
);
|
||||
|
||||
let data = serde_json::json!({
|
||||
"model_config": model_config,
|
||||
"input": payload,
|
||||
});
|
||||
writeln!(writer, "{}", serde_json::to_string(&data)?)?;
|
||||
|
||||
Ok(Self {
|
||||
Ok(Box::new(FileLogHandle {
|
||||
writer: Some(writer),
|
||||
temp_path,
|
||||
})
|
||||
logs_to_keep: self.logs_to_keep,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn write_json(&mut self, line: &serde_json::Value) -> Result<()> {
|
||||
impl RequestLogHandle for FileLogHandle {
|
||||
fn write(&mut self, s: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let writer = self
|
||||
.writer
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("logger is finished"))?;
|
||||
writeln!(writer, "{}", serde_json::to_string(line)?)?;
|
||||
writeln!(writer, "{}", s)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error<E>(&mut self, error: E) -> Result<()>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
self.write_json(&serde_json::json!({
|
||||
"error": format!("{}", error),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn write<Payload>(&mut self, data: &Payload, usage: Option<&Usage>) -> Result<()>
|
||||
where
|
||||
Payload: Serialize,
|
||||
{
|
||||
self.write_json(&serde_json::json!({
|
||||
"data": data,
|
||||
"usage": usage,
|
||||
}))
|
||||
}
|
||||
|
||||
impl FileLogHandle {
|
||||
fn finish(&mut self) -> Result<()> {
|
||||
if let Some(mut writer) = self.writer.take() {
|
||||
writer.flush()?;
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
let log_path = |i| logs_dir.join(format!("llm_request.{}.jsonl", i));
|
||||
|
||||
for i in (0..LOGS_TO_KEEP - 1).rev() {
|
||||
if self.logs_to_keep == 0 {
|
||||
fs_err::remove_file(&self.temp_path)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for i in (0..self.logs_to_keep.saturating_sub(1)).rev() {
|
||||
let _ = fs_err::rename(log_path(i), log_path(i + 1));
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +300,7 @@ impl RequestLog {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for RequestLog {
|
||||
impl Drop for FileLogHandle {
|
||||
fn drop(&mut self) {
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
|
|
@ -310,24 +314,6 @@ mod tests {
|
|||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_request_log_start_creates_logs_dir() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_PATH_ROOT", None::<&str>)]);
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
std::env::set_var("GOOSE_PATH_ROOT", temp_dir.path());
|
||||
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
assert!(!logs_dir.exists(), "logs dir should not exist yet");
|
||||
|
||||
let log = RequestLog::start(json!({"name": "test"}), &json!({"model": "test"}))
|
||||
.expect("RequestLog::start should create missing logs dir");
|
||||
drop(log);
|
||||
|
||||
assert!(logs_dir.is_dir(), "logs dir should have been created");
|
||||
|
||||
std::env::remove_var("GOOSE_PATH_ROOT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unescape_json_values_with_object() {
|
||||
let value = json!({"text": "Hello\\nWorld"});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue