Merge pull request #38914 from BerriAI/litellm_fix_skills_hook_import_side_effect

fix(proxy): register SkillsInjectionHook at proxy startup instead of import time
This commit is contained in:
Mateo Wang 2026-09-07 15:18:54 -07:00 committed by GitHub
commit 058d260509
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 15 deletions

View file

@ -21,10 +21,7 @@ from litellm.llms.litellm_proxy.skills import (
code_execution_handler,
get_litellm_code_execution_tool,
)
from litellm.proxy.hooks.litellm_skills.main import (
SkillsInjectionHook,
skills_injection_hook,
)
from litellm.proxy.hooks.litellm_skills.main import SkillsInjectionHook
__all__ = [
"LITELLM_CODE_EXECUTION_TOOL",
@ -35,5 +32,4 @@ __all__ = [
"SkillsSandboxExecutor",
"code_execution_handler",
"get_litellm_code_execution_tool",
"skills_injection_hook",
]

View file

@ -29,6 +29,7 @@ import json
from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any, Final, Protocol
import litellm
from litellm._logging import verbose_proxy_logger
from litellm.caching.caching import DualCache
from litellm.integrations.custom_logger import CustomLogger
@ -493,7 +494,6 @@ class SkillsInjectionHook(CustomLogger):
Returns the final response with generated files inline.
"""
import litellm
from litellm.llms.litellm_proxy.skills.code_execution import (
LiteLLMInternalTools,
)
@ -723,7 +723,6 @@ print('No executable skill module found')
Returns the final response with generated files inline.
"""
import litellm
from litellm.llms.litellm_proxy.skills.code_execution import (
LiteLLMInternalTools,
)
@ -913,11 +912,3 @@ print('No executable skill module found')
verbose_proxy_logger.debug("SkillsInjectionHook: Attached %s files to response", len(generated_files))
return response
# Global instance for registration
skills_injection_hook: Final = SkillsInjectionHook()
import litellm
litellm.logging_callback_manager.add_litellm_callback(skills_injection_hook)

View file

@ -1,3 +1,7 @@
import os
import subprocess
import sys
from typing import Final
from unittest.mock import AsyncMock, patch
import pytest
@ -65,3 +69,19 @@ async def test_execute_code_loop_dispatches_litellm_skill_tool():
mock_exec.assert_awaited_once()
assert mock_exec.await_args.args[0] == SKILL_TOOL_NAME
assert result is final_response
def test_importing_proxy_hooks_does_not_mutate_litellm_callbacks():
script: Final = (
"import litellm; "
"litellm.callbacks = []; "
"import litellm.proxy.hooks; "
"assert len(litellm.callbacks) == 0, f'mutated: {litellm.callbacks}'"
)
result: Final = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
env={**os.environ, "LITELLM_LOCAL_MODEL_COST_MAP": "True"},
)
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"