llm: reintroduce withPlugins

This was removed in #340510, as the previous implementation didn't work well.

However, it's still the method that's recommended in the patch for the
`llm install` command (`001-disable-install-uninstall-commands.patch`)
and with the additions to `enable` overrides on the top-level `llm`
derivation, there's a need for a place where the LLM plugin "directory"
is specified.

So, I've reintroduced the method with a completely new implementation.
I've also given it some protections against the list of names getting
out of date.

This might be a little too clever, but I think the user-facing interface
for this function is now quite nice.

Changed files
+143 -85
pkgs
by-name
ll
development
python-modules
+42 -77
pkgs/by-name/ll/llm/package.nix
···
-
{
-
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-<llm-plugin>`.
+
#
+
# 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-<plugin-name>"
+
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
+101 -8
pkgs/development/python-modules/llm/default.nix
···
{
lib,
+
runCommand,
callPackage,
buildPythonPackage,
fetchFromGitHub,
···
pluggy,
puremagic,
pydantic,
+
python,
python-ulid,
pyyaml,
sqlite-migrate,
···
sqlite-utils,
}:
let
+
# The function signature of `withPlugins` is the list of all the plugins `llm` knows about.
+
# The plugin directory is at <https://llm.datasette.io/en/stable/plugins/directory.html>
+
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/<hash>-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";
···
pythonImportsCheck = [ "llm" ];
passthru = {
-
inherit withPlugins;
+
inherit withPlugins withAllPlugins;
+
mkPluginTest = plugin: {
${plugin.pname} = callPackage ./mk-plugin-test.nix { inherit llm plugin; };
};
···
];
};
};
-
-
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