diff --git a/pkgs/by-name/ll/llm/package.nix b/pkgs/by-name/ll/llm/package.nix index d98edfd4c9cb..f2bb127d32aa 100644 --- a/pkgs/by-name/ll/llm/package.nix +++ b/pkgs/by-name/ll/llm/package.nix @@ -1,81 +1,46 @@ -{ - lib, - python3Packages, - enable-llm-anthropic ? false, - enable-llm-cmd ? false, - enable-llm-command-r ? false, - enable-llm-deepseek ? false, - enable-llm-fragments-github ? false, - enable-llm-fragments-pypi ? false, - enable-llm-gemini ? false, - enable-llm-grok ? false, - enable-llm-groq ? false, - enable-llm-gguf ? false, - enable-llm-hacker-news ? false, - enable-llm-jq ? false, - enable-llm-mistral ? false, - enable-llm-ollama ? false, - enable-llm-openai-plugin ? false, - enable-llm-openrouter ? false, - enable-llm-sentence-transformers ? false, - enable-llm-templates-fabric ? false, - enable-llm-templates-github ? false, - enable-llm-venice ? false, - enable-llm-video-frames ? false, -}: +# The `...` allows this derivation to be overridden with `enable-`. +# +# Example: +# +# ```nix +# llm.override { +# enable-llm-anthropic = true; +# enable-llm-gemini = true; +# enable-llm-cmd = true; +# enable-llm-templates-github = true; +# } +# ``` +# +# Whatever names are accepted by `llm.withPlugins` are accepted with an added `enable-` prefix as +# an override of this derivation. The user can also do `llm.withPlugins { llm-anthropic = true; }`. +{ lib, python3Packages, ... }@args: let - inherit (python3Packages) - toPythonApplication - llm - llm-anthropic - llm-cmd - llm-command-r - llm-deepseek - llm-fragments-github - llm-fragments-pypi - llm-gemini - llm-gguf - llm-grok - llm-groq - llm-hacker-news - llm-jq - llm-mistral - llm-ollama - llm-openai-plugin - llm-openrouter - llm-sentence-transformers - llm-templates-fabric - llm-templates-github - llm-venice - llm-video-frames - ; + inherit (python3Packages) llm; + + hasEnablePrefix = lib.hasPrefix "enable-"; + addEnablePrefix = name: "enable-${name}"; + removeEnablePrefix = lib.removePrefix "enable-"; + + # Filter to just the attributes which are named "enable-" + enableArgs = lib.filterAttrs (name: value: hasEnablePrefix name) args; + pluginArgs = lib.mapAttrs' ( + name: value: lib.nameValuePair (removeEnablePrefix name) value + ) enableArgs; + + # Provide some diagnostics for the plugin names + pluginNames = lib.attrNames (lib.functionArgs llm.withPlugins); + enableNames = lib.map addEnablePrefix pluginNames; + unknownPluginNames = lib.removeAttrs pluginArgs pluginNames; + unknownNames = lib.map addEnablePrefix (lib.attrNames unknownPluginNames); + unknownNamesDiagnostic = '' + Unknown plugins specified in override: ${lib.concatStringsSep ", " unknownNames} + + Valid overrides: + - ${lib.concatStringsSep "\n - " enableNames} + ''; in -toPythonApplication ( - llm.overrideAttrs (finalAttrs: { - propagatedBuildInputs = - (finalAttrs.propagatedBuildInputs or [ ]) - ++ lib.optionals enable-llm-anthropic [ llm-anthropic ] - ++ lib.optionals enable-llm-cmd [ llm-cmd ] - ++ lib.optionals enable-llm-cmd [ llm-command-r ] - ++ lib.optionals enable-llm-deepseek [ llm-deepseek ] - ++ lib.optionals enable-llm-fragments-github [ llm-fragments-github ] - ++ lib.optionals enable-llm-fragments-pypi [ llm-fragments-pypi ] - ++ lib.optionals enable-llm-gemini [ llm-gemini ] - ++ lib.optionals enable-llm-gguf [ llm-gguf ] - ++ lib.optionals enable-llm-grok [ llm-grok ] - ++ lib.optionals enable-llm-groq [ llm-groq ] - ++ lib.optionals enable-llm-hacker-news [ llm-hacker-news ] - ++ lib.optionals enable-llm-jq [ llm-jq ] - ++ lib.optionals enable-llm-mistral [ llm-mistral ] - ++ lib.optionals enable-llm-ollama [ llm-ollama ] - ++ lib.optionals enable-llm-openai-plugin [ llm-openai-plugin ] - ++ lib.optionals enable-llm-openrouter [ llm-openrouter ] - ++ lib.optionals enable-llm-sentence-transformers [ llm-sentence-transformers ] - ++ lib.optionals enable-llm-templates-fabric [ llm-templates-fabric ] - ++ lib.optionals enable-llm-templates-github [ llm-templates-github ] - ++ lib.optionals enable-llm-venice [ llm-venice ] - ++ lib.optionals enable-llm-video-frames [ llm-video-frames ]; - }) -) +assert lib.assertMsg (lib.length unknownNames == 0) unknownNamesDiagnostic; + +llm.withPlugins pluginArgs diff --git a/pkgs/development/python-modules/llm/default.nix b/pkgs/development/python-modules/llm/default.nix index 477dd2dc48c4..8f13f25783b8 100644 --- a/pkgs/development/python-modules/llm/default.nix +++ b/pkgs/development/python-modules/llm/default.nix @@ -1,5 +1,6 @@ { lib, + runCommand, callPackage, buildPythonPackage, fetchFromGitHub, @@ -14,6 +15,7 @@ pluggy, puremagic, pydantic, + python, python-ulid, pyyaml, sqlite-migrate, @@ -23,6 +25,103 @@ sqlite-utils, }: let + # The function signature of `withPlugins` is the list of all the plugins `llm` knows about. + # The plugin directory is at + withPluginsArgNames = lib.functionArgs withPlugins; + + /** + Make a derivation for `llm` that contains `llm` plus the relevant plugins. + + # Type + + ``` + withPlugins :: + { + llm-anthropic :: bool, + llm-gemini :: bool, + ... + } + -> derivation + ``` + + See `lib.attrNames (lib.functionArgs llm.withPlugins)` for the total list of plugins supported. + + # Examples + :::{.example} + ## `llm.withPlugins` usage example + + ```nix + llm.withPlugins { llm-gemini = true; llm-groq = true; } + => «derivation /nix/store/-python3-3.12.10-llm-with-llm-gemini-llm-groq.drv» + ``` + + ::: + */ + withPlugins = + # Keep this list up to date with the plugins in python3Packages! + { + llm-anthropic ? false, + llm-cmd ? false, + llm-command-r ? false, + llm-deepseek ? false, + llm-fragments-github ? false, + llm-fragments-pypi ? false, + llm-gemini ? false, + llm-gguf ? false, + llm-grok ? false, + llm-groq ? false, + llm-hacker-news ? false, + llm-jq ? false, + llm-mistral ? false, + llm-ollama ? false, + llm-openai-plugin ? false, + llm-openrouter ? false, + llm-sentence-transformers ? false, + llm-templates-fabric ? false, + llm-templates-github ? false, + llm-venice ? false, + llm-video-frames ? false, + }@args: + let + # Filter to just the attributes which are set to a true value. + setArgs = lib.filterAttrs (name: lib.id) args; + + # Make a string with those names separated with a dash. + setArgsStr = lib.concatStringsSep "-" (lib.attrNames setArgs); + + # Make the derivation name reflect what's inside it. + drvName = if lib.stringLength setArgsStr == 0 then "llm" else "llm-with-${setArgsStr}"; + + # Make a python environment with just those plugins. + python-environment = python.withPackages ( + ps: + let + # Throw a diagnostic if this list gets out of sync with the names in python3Packages + allPluginsPresent = pluginNames == (lib.attrNames withPluginsArgNames); + pluginNames = lib.attrNames (lib.intersectAttrs ps withPluginsArgNames); + missingNamesList = lib.attrNames (lib.removeAttrs withPluginsArgNames pluginNames); + missingNames = lib.concatStringsSep ", " missingNamesList; + + # The relevant plugins are the ones the user asked for. + plugins = lib.intersectAttrs setArgs ps; + in + assert lib.assertMsg allPluginsPresent "Missing these plugins: ${missingNames}"; + ([ ps.llm ] ++ lib.attrValues plugins) + ); + + in + # That Python environment produced above contains too many irrelevant binaries, due to how + # Python needs to use propagatedBuildInputs. Let's make one with just what's needed: `llm`. + # Since we include the `passthru` and `meta` information, it's as good as the original + # derivation. + runCommand "${python.name}-${drvName}" { inherit (llm) passthru meta; } '' + mkdir -p $out/bin + ln -s ${python-environment}/bin/llm $out/bin/llm + ''; + + # Uses the `withPlugins` names to make a Python environment with everything. + withAllPlugins = withPlugins (lib.genAttrs (lib.attrNames withPluginsArgNames) (name: true)); + llm = buildPythonPackage rec { pname = "llm"; version = "0.25"; @@ -75,7 +174,8 @@ let pythonImportsCheck = [ "llm" ]; passthru = { - inherit withPlugins; + inherit withPlugins withAllPlugins; + mkPluginTest = plugin: { ${plugin.pname} = callPackage ./mk-plugin-test.nix { inherit llm plugin; }; }; @@ -93,12 +193,5 @@ let ]; }; }; - - withPlugins = throw '' - llm.withPlugins was confusing to use and has been removed. - Please migrate to using python3.withPackages(ps: [ ps.llm ]) instead. - - See https://nixos.org/manual/nixpkgs/stable/#python.withpackages-function for more usage examples. - ''; in llm