88from oss_crs .src .templates .renderer import render_run_crs_compose_docker_compose
99
1010
11- def _patch_renderer (monkeypatch , build_env_fn = None ):
11+ def _patch_renderer (monkeypatch , build_env_fn = None , llm_context_fn = None ):
1212 """Stub out the two external collaborators the renderer reaches for.
1313
1414 Pass a custom ``build_env_fn`` to observe the kwargs the renderer passes
1515 to ``build_run_service_env``; otherwise a minimal fixed response is used.
16+ Pass ``llm_context_fn`` to render an LLM-backed compose; otherwise no LLM
17+ context is produced and the litellm services are omitted.
1618 """
1719 monkeypatch .setattr (
1820 "oss_crs.src.templates.renderer.build_run_service_env" ,
@@ -25,7 +27,7 @@ def _patch_renderer(monkeypatch, build_env_fn=None):
2527 )
2628 monkeypatch .setattr (
2729 "oss_crs.src.templates.renderer.prepare_llm_context" ,
28- lambda * _args , ** _kwargs : None ,
30+ llm_context_fn or ( lambda * _args , ** _kwargs : None ) ,
2931 )
3032
3133
@@ -796,3 +798,61 @@ def test_target_independent_module_renders_unhashed_image_no_build(
796798 service = yaml .safe_load (rendered )["services" ]["crs-shelley_lsp" ]
797799 assert service ["image" ] == "oss-crs-runner:crs-shelley-lsp"
798800 assert "build" not in service
801+
802+
803+ # ---------------------------------------------------------------------------
804+ # Internal LiteLLM proxy: offline gate on the model cost map
805+ # ---------------------------------------------------------------------------
806+
807+
808+ def _internal_llm_context (tmp_path : Path ):
809+ """A ``llm_context_fn`` yielding an internal-mode LiteLLM stack."""
810+ return lambda * _args , ** _kwargs : {
811+ "mode" : "internal" ,
812+ "litellm_env_secret_files" : {"OPENAI_API_KEY" : str (tmp_path / "sec" )},
813+ "litellm_config_path" : str (tmp_path / "litellm-config.yaml" ),
814+ "key_gen_request_path" : str (tmp_path / "key_gen_request.yaml" ),
815+ "secret_files" : {},
816+ "api_keys" : {},
817+ }
818+
819+
820+ @pytest .mark .parametrize (
821+ "offline,expected_env" ,
822+ [
823+ pytest .param (True , ["LITELLM_LOCAL_MODEL_COST_MAP=True" ], id = "offline" ),
824+ pytest .param (False , None , id = "online" ),
825+ ],
826+ )
827+ def test_offline_gates_litellm_local_cost_map_env (
828+ monkeypatch , tmp_path : Path , offline : bool , expected_env : list | None
829+ ) -> None :
830+ """``--offline`` pins litellm to the cost map bundled in its image.
831+
832+ Offline the fetch of model_prices_and_context_window.json cannot succeed and
833+ litellm falls back to that bundled copy regardless, so the attempt is
834+ skipped. Online the fetch is left alone and live pricing is preserved.
835+ """
836+ _patch_renderer (monkeypatch , llm_context_fn = _internal_llm_context (tmp_path ))
837+
838+ crs_compose = _make_crs_compose (tmp_path , [_make_crs (tmp_path , "crs-libfuzzer" )])
839+ crs_compose .offline = offline
840+ target = _make_target (tmp_path , has_repo = False )
841+
842+ rendered , _ = _render (crs_compose , target , tmp_path )
843+
844+ litellm_service = yaml .safe_load (rendered )["services" ]["oss-crs-litellm" ]
845+ assert litellm_service .get ("environment" ) == expected_env , (
846+ f"offline={ offline } must render environment { expected_env } ; "
847+ f"got: { litellm_service .get ('environment' )} "
848+ )
849+
850+ # Ensure we set a start period; also needed for online - Prisma migrations are slow
851+ assert "start_period" in litellm_service ["healthcheck" ]
852+
853+ # The proxy is started from secret-derived exports in `command`; adding an
854+ # `environment:` key must not displace them.
855+ command = " " .join (litellm_service ["command" ])
856+ assert "$(cat /run/secrets/litellm_env_OPENAI_API_KEY)" in command , (
857+ f"secret-derived exports missing from command; got: { command } "
858+ )
0 commit comments