mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(messages): keep the provider out of a shared logger's litellm_params on native messages calls
Python's messages handler only records custom_llm_provider in model_call_details. The native adapter also wrote it into litellm_params after every provider result, and the proxy reuses one logger across router hops, so a fallback hop's pre_call log showed the provider where Python shows null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013FDwtkaNTifWAP4W7JZavE
This commit is contained in:
parent
1d46ade6da
commit
0b52efe0bf
2 changed files with 81 additions and 3 deletions
|
|
@ -525,7 +525,11 @@ impl LegacyLogging {
|
|||
.getattr("litellm_params")?
|
||||
.cast_into::<PyDict>()?
|
||||
.copy()?;
|
||||
params.set_item("custom_llm_provider", &facts.provider.provider)?;
|
||||
if self.operation != LoggingOperation::Messages {
|
||||
// Python's Messages handler hands the provider to the logger beside its params, never
|
||||
// in them, and the proxy's logger carries over to the next fallback hop.
|
||||
params.set_item("custom_llm_provider", &facts.provider.provider)?;
|
||||
}
|
||||
crate::python::Logging::Update.call(
|
||||
py,
|
||||
(
|
||||
|
|
@ -1142,6 +1146,52 @@ assert logger.update['custom_llm_provider'] == 'edenai', logger.update
|
|||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::messages(crate::LoggingOperation::Messages, false)]
|
||||
#[case::ocr(crate::LoggingOperation::Ocr, true)]
|
||||
fn result_ready_puts_the_provider_in_the_logged_params_except_for_messages(
|
||||
#[case] operation: crate::LoggingOperation,
|
||||
#[case] in_params: bool,
|
||||
) {
|
||||
Python::initialize();
|
||||
Python::attach(|py| {
|
||||
let locals = namespace(py, PAYLOAD_LOGGER);
|
||||
run(
|
||||
py,
|
||||
&locals,
|
||||
c"
|
||||
logger.litellm_params = {}
|
||||
logger.optional_params = {}
|
||||
logger.model_call_details = {}
|
||||
logger._provider_reported_cost = None
|
||||
",
|
||||
);
|
||||
let mut logging = LegacyLogging {
|
||||
logger: Some(PythonLogger::new(local(&locals, "logger").unbind())),
|
||||
operation,
|
||||
..legacy_call(py, &locals, false)
|
||||
};
|
||||
let facts = litellm_host::interceptors::ExecutionFacts {
|
||||
provider: litellm_host::interceptors::ProviderIdentity {
|
||||
model: "native-test".into(),
|
||||
provider: "anthropic".into(),
|
||||
},
|
||||
cost: litellm_host::interceptors::Cost::Deferred,
|
||||
source: litellm_host::interceptors::ResultSource::Provider,
|
||||
};
|
||||
logging.result_ready(py, &facts).unwrap();
|
||||
locals.set_item("in_params", in_params).unwrap();
|
||||
run(
|
||||
py,
|
||||
&locals,
|
||||
c"
|
||||
assert logger.update['custom_llm_provider'] == 'anthropic', logger.update
|
||||
assert ('custom_llm_provider' in logger.update['litellm_params']) is in_params, logger.update
|
||||
",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const DOCUMENT: &str = "data:application/pdf;base64,YWJj";
|
||||
const EDITED: &str = "data:application/pdf;base64,ZWRpdGVk";
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@ from pydantic import TypeAdapter
|
|||
import litellm
|
||||
from litellm.integrations.argilla import ArgillaLogger
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.types.integrations.argilla import ArgillaItem
|
||||
from litellm.types.utils import ModelResponse, StandardLoggingPayload
|
||||
from tests.test_litellm_rust.messages.data import BAD_REQUEST, MESSAGES_RESPONSE, RATE_LIMITED, call
|
||||
from tests.test_litellm_rust.messages.data import BAD_REQUEST, MESSAGES_RESPONSE, RATE_LIMITED, REQUEST, call
|
||||
from tests.test_litellm_rust.support.callback_recorder import HookRecorder, RecordingLogger, request_body
|
||||
from tests.test_litellm_rust.support.isolation import rebound
|
||||
from tests.test_litellm_rust.support.recording_server import RecordingServer, ResponseSpec, relaxed
|
||||
from tests.test_litellm_rust.support.results import response_id, with_id
|
||||
from tests.test_litellm_rust.support.routing import assert_served_natively
|
||||
from tests.test_litellm_rust.support.routing import CALL_ID, assert_served_natively
|
||||
|
||||
pytestmark = pytest.mark.requires_rust_extension
|
||||
|
||||
|
|
@ -110,6 +111,33 @@ async def test_pre_api_call_sees_the_wire_body(server: RecordingServer) -> None:
|
|||
assert request_body(pre_call) == server.requests[0].body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pre_api_call_of_a_retried_call_keeps_the_provider_out_of_the_litellm_params(
|
||||
server: RecordingServer,
|
||||
) -> None:
|
||||
recorder: Final = RecordingLogger()
|
||||
logging_obj: Final = Logging(
|
||||
model=REQUEST["model"],
|
||||
messages=[],
|
||||
stream=False,
|
||||
start_time=datetime.now(),
|
||||
call_type="anthropic_messages",
|
||||
litellm_call_id=CALL_ID,
|
||||
function_id=CALL_ID,
|
||||
dynamic_input_callbacks=[recorder],
|
||||
)
|
||||
|
||||
await call(server, litellm_logging_obj=logging_obj)
|
||||
await call(server, litellm_logging_obj=logging_obj)
|
||||
|
||||
pre_calls: Final = recorder.wait_for("log_pre_api_call", count=2)
|
||||
|
||||
assert_served_natively(server, count=2)
|
||||
for pre_call in pre_calls:
|
||||
assert isinstance(pre_call.kwargs, dict)
|
||||
assert "custom_llm_provider" not in _OBJECT.validate_python(pre_call.kwargs["litellm_params"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pre_api_call_edits_reach_the_provider(server: RecordingServer) -> None:
|
||||
class Edit(CustomLogger):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue