Files
clan-llm/flake.nix

224 lines
7.2 KiB
Nix

{
description = "LLM-powered natural language interface for Clan configuration";
inputs = {
clan-core.url = "git+https://git.clan.lol/collinarnett/clan-core.git?ref=collinarnett-export_container_driver";
nixpkgs.follows = "clan-core/nixpkgs";
flake-parts.follows = "clan-core/flake-parts";
treefmt-nix.follows = "clan-core/treefmt-nix";
mcp-servers-nix.url = "github:natsukium/mcp-servers-nix";
mcp-servers-nix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{
self,
clan-core,
flake-parts,
mcp-servers-nix,
...
}:
flake-parts.lib.mkFlake { inherit inputs; } (
{ ... }:
{
imports = [
# Import clan flakeModule to provide clanInternals structure for testing
# This only creates Nix data structures, doesn't execute clan-cli
clan-core.flakeModules.clan
clan-core.flakeModules.containerTest
mcp-servers-nix.flakeModule
./formatter.nix
./machines/flake-module.nix
];
systems = [
"x86_64-linux"
"aarch64-darwin"
];
# Minimal clan configuration for AI testing
# This creates the clanInternals structure that clan ai queries
flake.clan = {
meta.name = "clan-llm-dev";
meta.description = "Development environment for Clan AI/LLM features";
# Define a minimal test machine for AI to discover
inventory.machines = {
test-machine = {
name = "test-machine";
description = "Test machine for AI development";
tags = [ "all" ];
};
};
};
perSystem =
{
config,
pkgs,
system,
...
}:
let
python = pkgs.python313;
# Override clan-cli to replace the llm and ai directories with our local versions
clanCLIPlusLLM = clan-core.packages.${system}.clan-cli.overrideAttrs (oldAttrs: {
postUnpack = (oldAttrs.postUnpack or "") + ''
rm -rf $sourceRoot/clan_lib/llm
rm -rf $sourceRoot/clan_cli/ai
cp -r ${./pkgs/clan-llm/clan_lib/llm} $sourceRoot/clan_lib/llm
cp -r ${./pkgs/clan-llm/clan_cli/ai} $sourceRoot/clan_cli/ai
'';
# Override sourceWithTests to include local LLM code for tests
passthru = oldAttrs.passthru // {
sourceWithTests = pkgs.runCommand "${oldAttrs.passthru.sourceWithTests.name}-with-llm" { } ''
cp -r ${oldAttrs.passthru.sourceWithTests} $out
chmod -R +w $out
rm -rf $out/clan_lib/llm
rm -rf $out/clan_cli/ai
cp -r ${./pkgs/clan-llm/clan_lib/llm} $out/clan_lib/llm
cp -r ${./pkgs/clan-llm/clan_cli/ai} $out/clan_cli/ai
'';
};
});
clanCLIMinusLLM = clan-core.packages.${system}.clan-cli.overrideAttrs (oldAttrs: {
postUnpack = (oldAttrs.postUnpack or "") + ''
rm -rf $sourceRoot/clan_lib/llm
rm -rf $sourceRoot/clan_cli/ai
'';
# Disable checks since clan --help would fail without AI module
doCheck = false;
doInstallCheck = false;
});
deepeval = python.pkgs.buildPythonPackage rec {
pname = "deepeval";
version = "3.8.4";
pyproject = true;
src = pkgs.fetchPypi {
inherit pname version;
hash = "sha256-y2DEpTSIlwE26BSd5L6zoOEnL+trjsKo7mM8oq07vOc=";
};
build-system = [ python.pkgs.poetry-core ];
pythonRelaxDeps = [ "posthog" ];
dependencies = with python.pkgs; [
anthropic
setuptools
requests
tqdm
pytest
pytest-xdist
pytest-repeat
pytest-rerunfailures
pytest-asyncio
tabulate
sentry-sdk
rich
portalocker
openai
aiohttp
typer
click
nest-asyncio
tenacity
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-otlp-proto-grpc
grpcio
posthog
pyfiglet
python-dotenv
pydantic
pydantic-settings
jinja2
];
# Tests require network access and API keys
doCheck = false;
};
# Python dependencies needed for development
pyTestDeps =
ps: with ps; [
pytest
pytest-subprocess
pytest-xdist
pytest-timeout
pytest-cov
];
# Development wrapper for clan that preserves PYTHONPATH
# This ensures sitecustomize.py is loaded to shim local ai/llm modules
clanDevWrapper = pkgs.writeShellScriptBin "clan" ''
export PYTHONPATH="$PWD:''${PYTHONPATH}"
exec ${
python.withPackages (ps: [ (ps.toPythonModule clanCLIMinusLLM) ])
}/bin/python -m clan_cli "$@"
'';
in
{
# Configure MCP servers
mcp-servers = {
programs.context7.enable = true;
flavors.claude-code.enable = true;
};
packages = {
default = clanCLIPlusLLM;
clan-cli = clanCLIPlusLLM;
};
devShells.default = pkgs.mkShell {
inputsFrom = [ config.mcp-servers.devShell ];
packages = [
# Put wrapper first so it takes priority over python env's clan binary
clanDevWrapper
(python.withPackages (
ps:
[ (ps.toPythonModule clanCLIMinusLLM) ]
++ (pyTestDeps ps)
++ [
ps.ipython
deepeval
]
))
pkgs.ruff
pkgs.mypy
];
# Set CLAN_CORE_PATH for tests that need to create flakes
CLAN_CORE_PATH = clan-core;
shellHook = ''
# Add project root to PYTHONPATH so Python finds sitecustomize.py
export PYTHONPATH="$PWD:$PYTHONPATH"
# Warn if ANTHROPIC_API_KEY is not set
if [ -z "''${ANTHROPIC_API_KEY:-}" ]; then
echo "WARNING: ANTHROPIC_API_KEY is not set."
echo "Please append --model=ollama to clan ai commands, or set ANTHROPIC_API_KEY."
fi
'';
};
# Container tests using the new flake-parts module
containerTests = {
llm = import ./checks/llm {
inherit self pkgs;
clan-core = inputs.clan-core;
};
};
};
}
);
}