diff options
author | Ben Sima <ben@bsima.me> | 2025-02-04 21:18:03 -0500 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2025-02-04 21:18:03 -0500 |
commit | 9f5b334eb6d0f64460f14d76255b096777a46332 (patch) | |
tree | db357ca2a933456e1f35c17a1c85000fb5a64b9d | |
parent | 86ea51353c223cdc82cb8ebd013d58d70a7e646a (diff) |
Update ollama, llm-ollama, openai-python, llm
I couldn't use llm-ollama because it required some package upgrades, so I
started going down that rabbit hole and ended up 1) realizing that these
packages are way out of date now, and 2) fiddling with overrides to get
everything to work. I finally figured it out, the `postPatch` in ollama-python
was throwing me off for like half a day.
Anyway, one thing to note is that these are changing fast and I need to either
move onto nixpkgs unstable for python stuff, or maintain my own builds of all of
these. Not sure which is more appropriate right now.
Oh and I had to fixup some logging stuff in Biz/Storybook.py because ruff
started complaining about something, which is weird because I don't think the
version changed? But it was easy enough to change.
-rwxr-xr-x | Biz/Que/Client.py | 34 | ||||
-rwxr-xr-x | Biz/Storybook.py | 22 | ||||
-rw-r--r-- | Omni/Bild/Deps.nix | 4 | ||||
-rw-r--r-- | Omni/Bild/Deps/Python.nix | 2 | ||||
-rw-r--r-- | Omni/Bild/Deps/llm-ollama.nix | 57 | ||||
-rw-r--r-- | Omni/Bild/Deps/openai-python.nix | 99 | ||||
-rw-r--r-- | Omni/Bild/Nixpkgs.nix | 52 | ||||
-rw-r--r-- | Omni/Bild/Python.nix | 17 | ||||
-rw-r--r-- | Omni/Bild/Sources.json | 37 | ||||
-rw-r--r-- | Omni/Log.py | 4 | ||||
-rwxr-xr-x | Omni/Repl.py | 9 |
11 files changed, 252 insertions, 85 deletions
diff --git a/Biz/Que/Client.py b/Biz/Que/Client.py index 671b464..6248a79 100755 --- a/Biz/Que/Client.py +++ b/Biz/Que/Client.py @@ -22,10 +22,17 @@ RETRIES = 10 DELAY = 3 BACKOFF = 1 +LOGLEVEL = logging.ERROR +LOG = logging.basicConfig( + format="%(asctime)s: %(levelname)s: %(message)s", + level=LOGLEVEL, + datefmt="%Y.%m.%d..%H.%M.%S", +) + def auth(args: argparse.Namespace) -> str | None: """Return the auth key for the given ns from ~/.config/que.conf.""" - logging.debug("auth") + LOG.debug("auth") namespace = args.target.split("/")[0] if namespace == "pub": return None @@ -48,7 +55,7 @@ def autodecode(bytestring: bytes) -> typing.Any: <https://docs.python.org/3/library/codecs.html#standard-encodings> """ - logging.debug("autodecode") + LOG.debug("autodecode") codecs = ["utf-8", "ascii"] for codec in codecs: try: @@ -75,8 +82,8 @@ def retry( try: return func(*args, **kwargs) except exception as ex: - logging.debug(ex) - logging.debug("retrying...") + LOG.debug(ex) + LOG.debug("retrying...") time.sleep(mdelay) mtries -= 1 mdelay *= backoff @@ -93,7 +100,7 @@ def retry( @retry(http.client.RemoteDisconnected) def send(args: argparse.Namespace) -> None: """Send a message to the que.""" - logging.debug("send") + LOG.debug("send") key = auth(args) data = args.infile req = request.Request(f"{args.host}/{args.target}") @@ -102,7 +109,7 @@ def send(args: argparse.Namespace) -> None: if key: req.add_header("Authorization", key) if args.serve: - logging.debug("serve") + LOG.debug("serve") while not time.sleep(1): request.urlopen(req, data=data, timeout=MAX_TIMEOUT) else: @@ -112,7 +119,7 @@ def send(args: argparse.Namespace) -> None: def then(args: argparse.Namespace, msg: str) -> None: """Perform an action when passed `--then`.""" if args.then: - logging.debug("then") + LOG.debug("then") subprocess.run( # noqa: S602 args.then.format(msg=msg, que=args.target), check=False, @@ -131,7 +138,7 @@ def recv(args: argparse.Namespace) -> None: Raises: ValueError: if url is malformed """ - logging.debug("recv on: %s", args.target) + LOG.debug("recv on: %s", args.target) if args.poll: req = request.Request(f"{args.host}/{args.target}/stream") else: @@ -145,12 +152,12 @@ def recv(args: argparse.Namespace) -> None: raise ValueError(msg) with request.urlopen(req) as _req: if args.poll: - logging.debug("polling") + LOG.debug("polling") while not time.sleep(1): reply = _req.readline() if reply: msg = autodecode(reply) - logging.debug("read") + LOG.debug("read") sys.stdout.write(msg) then(args, msg) else: @@ -223,11 +230,8 @@ def main() -> None: sys.stdout.write("ok\n") sys.exit() if argv.debug: - logging.basicConfig( - format="%(asctime)s: %(levelname)s: %(message)s", - level=logging.DEBUG, - datefmt="%Y.%m.%d..%H.%M.%S", - ) + global LOGLEVEL # noqa: PLW0603 + LOGLEVEL = logging.DEBUG try: if argv.infile: send(argv) diff --git a/Biz/Storybook.py b/Biz/Storybook.py index 7d8f326..dbaf82a 100755 --- a/Biz/Storybook.py +++ b/Biz/Storybook.py @@ -56,6 +56,7 @@ PORT = int(os.environ.get("PORT", "3000")) area = App.from_env() app = ludic.web.LudicApp(debug=area == App.Area.Test) +log = Log.setup(logging.DEBUG if area == App.Area.Test else logging.ERROR) Sqids = sqids.Sqids() @@ -70,14 +71,13 @@ def main() -> None: def move(area: App.Area) -> None: """Run the application.""" - Log.setup(logging.DEBUG if area == App.Area.Test else logging.ERROR) - logging.info("area: %s", area) + log.info("area: %s", area) # during test, bind to beryllium's VPN address, else localhost host = "100.127.197.132" if area == App.Area.Test else "127.0.0.1" uvicorn.run(app, host=host, port=PORT) -def test(area: App.Area = App.Area.Test) -> None: +def test(area: App.Area) -> None: """Run the unittest suite manually.""" Test.run(area, [IndexTest, StoryTest]) @@ -238,7 +238,7 @@ def _openai_generate_text( }, ] client = openai.OpenAI() - logging.debug("calling openai.chat.completions.create") + log.debug("calling openai.chat.completions.create") return client.chat.completions.create( model="gpt-4o-mini", messages=messages, @@ -293,15 +293,15 @@ def generate_image( Raises: InternalServerError: when OpenAI API fails """ - logging.info("generating image %s.%s", story_id, page) + log.info("generating image %s.%s", story_id, page) url = None if area == App.Area.Test: time.sleep(1) url = "https://placehold.co/1024.png" else: client = openai.OpenAI() - logging.debug("calling openai.images.generate") - logging.debug("prompt: %s", image_prompt) + log.debug("calling openai.images.generate") + log.debug("prompt: %s", image_prompt) image_response = client.images.generate( model="dall-e-3", prompt=image_prompt, @@ -312,7 +312,7 @@ def generate_image( url = image_response.data[0].url if url is None: msg = "error getting image from OpenAI" - logging.error(msg) + log.error(msg) raise ludic.web.exceptions.InternalServerError(msg) image = Image( story_id=story_id, @@ -349,7 +349,7 @@ def generate_story_in_background( inputs, ), ) - logging.info("starting job %s", job_id) + log.info("starting job %s", job_id) thread.start() story = Story(id=story_id, inputs=inputs) # save stuff @@ -364,7 +364,7 @@ def generate_story_pages( inputs: StoryInputs, ) -> list[Page]: """Upsert a new story.""" - logging.info("generating story pages %s", story_id) + log.info("generating story pages %s", story_id) story_resp = generate_pages(inputs) pages = [ Page( @@ -527,7 +527,7 @@ def images_static(story_id: str, page: int) -> ludic.web.responses.Response: if image["path"].exists(): return ludic.web.responses.FileResponse(image["path"]) msg = "images_static: image not found" - logging.error(msg) + log.error(msg) raise ludic.web.exceptions.NotFoundError(msg) diff --git a/Omni/Bild/Deps.nix b/Omni/Bild/Deps.nix index 0b23f9f..f25c947 100644 --- a/Omni/Bild/Deps.nix +++ b/Omni/Bild/Deps.nix @@ -24,11 +24,9 @@ _self: super: { ]; }; - llm = super.overrideSrc super.llm super.sources.llm; - nostr-rs-relay = super.callPackage ./Deps/nostr-rs-relay.nix {}; - ollama = super.ollama.override {acceleration = "cuda";}; + ollama = super.unstable.ollama.override {acceleration = "cuda";}; radicale = super.radicale.overrideAttrs (_old: {doCheck = false;}); } diff --git a/Omni/Bild/Deps/Python.nix b/Omni/Bild/Deps/Python.nix index bb01139..3a0562d 100644 --- a/Omni/Bild/Deps/Python.nix +++ b/Omni/Bild/Deps/Python.nix @@ -2,9 +2,11 @@ "cryptography" "flask" "llm" + "llm-ollama" "ludic" "mypy" "nltk" + "ollama" "openai" "requests" "slixmpp" diff --git a/Omni/Bild/Deps/llm-ollama.nix b/Omni/Bild/Deps/llm-ollama.nix index 15b26cc..3956bb7 100644 --- a/Omni/Bild/Deps/llm-ollama.nix +++ b/Omni/Bild/Deps/llm-ollama.nix @@ -1,47 +1,62 @@ { + lib, buildPythonPackage, fetchFromGitHub, - lib, + # build-system + setuptools, llm, + # dependencies + click, ollama, + pydantic, + # tests pytestCheckHook, - setuptools, - pythonOlder, }: buildPythonPackage rec { pname = "llm-ollama"; - version = "0.3.0"; + version = "0.8.2"; pyproject = true; - disabled = pythonOlder "3.8"; - src = fetchFromGitHub { owner = "taketwo"; - repo = pname; - rev = "refs/tags/${version}"; - hash = "sha256-Ar0Ux8BNGY0i764CEk7+48J6jnndlRIIMPZ9tFpXiy4="; + repo = "llm-ollama"; + tag = version; + hash = "sha256-/WAugfkI4izIQ7PoKM9epd/4vFxYPvsiwDbEqqTdMq4="; }; - nativeBuildInputs = [setuptools]; + build-system = [ + setuptools + # Follows the reasoning from https://github.com/NixOS/nixpkgs/pull/327800#discussion_r1681586659 about including llm in build-system + llm + ]; - buildInputs = [llm ollama]; + dependencies = [ + click + ollama + pydantic + ]; - propagatedBuildInputs = [ollama]; + nativeCheckInputs = [ + pytestCheckHook + ]; + # These tests try to access the filesystem and fail disabledTests = [ - # wants to mkdir in the /homeless-shelter - "test_registered_models" + "test_registered_model" + "test_registered_chat_models" + "test_registered_embedding_models" + "test_registered_models_when_ollama_is_down" ]; - nativeCheckInputs = [pytestCheckHook]; - - pythonImportsCheck = ["llm_ollama"]; + pythonImportsCheck = [ + "llm_ollama" + ]; - meta = with lib; { + meta = { + description = "LLM plugin providing access to Ollama models using HTTP API"; homepage = "https://github.com/taketwo/llm-ollama"; - description = "LLM plugin providing access to local Ollama models usting HTTP API"; changelog = "https://github.com/taketwo/llm-ollama/releases/tag/${version}"; - license = licenses.asl20; - maintainers = with maintainers; [bsima]; + license = lib.licenses.asl20; + maintainers = with lib.maintainers; [erethon]; }; } diff --git a/Omni/Bild/Deps/openai-python.nix b/Omni/Bild/Deps/openai-python.nix new file mode 100644 index 0000000..79db11c --- /dev/null +++ b/Omni/Bild/Deps/openai-python.nix @@ -0,0 +1,99 @@ +{ + lib, + buildPythonPackage, + pythonOlder, + # build-system + hatchling, + hatch-fancy-pypi-readme, + # dependencies + anyio, + distro, + httpx, + jiter, + pydantic, + sniffio, + tqdm, + typing-extensions, + numpy, + pandas, + pandas-stubs, + # check deps + pytestCheckHook, + dirty-equals, + inline-snapshot, + nest-asyncio, + pytest-asyncio, + pytest-mock, + respx, + sources, +}: +buildPythonPackage rec { + pname = "openai"; + version = sources.openai-python.version; + pyproject = true; + + disabled = pythonOlder "3.8"; + + src = sources.openai-python; + + build-system = [ + hatchling + hatch-fancy-pypi-readme + ]; + + dependencies = [ + anyio + distro + httpx + jiter + pydantic + sniffio + tqdm + typing-extensions + ]; + + optional-dependencies = { + datalib = [ + numpy + pandas + pandas-stubs + ]; + }; + + pythonImportsCheck = ["openai"]; + + nativeCheckInputs = [ + pytestCheckHook + dirty-equals + inline-snapshot + nest-asyncio + pytest-asyncio + pytest-mock + respx + ]; + + pytestFlagsArray = [ + "-W" + "ignore::DeprecationWarning" + ]; + + disabledTests = [ + # Tests make network requests + "test_copy_build_request" + "test_basic_attribute_access_works" + ]; + + disabledTestPaths = [ + # Test makes network requests + "tests/api_resources" + ]; + + meta = with lib; { + description = "Python client library for the OpenAI API"; + homepage = "https://github.com/openai/openai-python"; + changelog = "https://github.com/openai/openai-python/releases/tag/v${version}"; + license = licenses.mit; + maintainers = with maintainers; [malo]; + mainProgram = "openai"; + }; +} diff --git a/Omni/Bild/Nixpkgs.nix b/Omni/Bild/Nixpkgs.nix index fb9a6b1..3418673 100644 --- a/Omni/Bild/Nixpkgs.nix +++ b/Omni/Bild/Nixpkgs.nix @@ -15,25 +15,35 @@ let # package overlays, because of the 'null' from 'overrideSource' depsOverlay = _: pkgs: pkgs.overridePinnedDeps pkgs.overrideSource; - overlays = [ - (_: _: {inherit sources;}) - (import ./CcacheWrapper.nix) - (import ./Functions.nix) - depsOverlay - (import ./Deps.nix) - (import ./Python.nix) - (import ./Haskell.nix) - # backport newer packages from unstable - (_: _: {unstable = nixos-unstable-small.pkgs;}) - (import "${sources.nvidia-patch-nixos}/overlay.nix") - ]; + this = { + nixos-24_11 = import sources.nixos-24_11 { + inherit system config; + overlays = [ + (_: _: {inherit sources;}) + (import ./CcacheWrapper.nix) + (import ./Functions.nix) + depsOverlay + (_: _: {unstable = this.nixos-unstable-small.pkgs;}) + (import ./Deps.nix) + (import ./Python.nix) + (import ./Haskell.nix) + (import "${sources.nvidia-patch-nixos}/overlay.nix") + ]; + }; - nixos-unstable-small = - import sources.nixos-unstable-small {inherit system config overlays;}; -in { - nixos-24_05 = import sources.nixos-24_05 {inherit system config overlays;}; - - nixos-24_11 = import sources.nixos-24_11 {inherit system config overlays;}; - - inherit nixos-unstable-small; -} + nixos-unstable-small = import sources.nixos-unstable-small { + inherit system config; + overlays = [ + (_: _: {inherit sources;}) + (import ./CcacheWrapper.nix) + (import ./Functions.nix) + depsOverlay + (import ./Deps.nix) + (import ./Python.nix) + (import ./Haskell.nix) + (import "${sources.nvidia-patch-nixos}/overlay.nix") + ]; + }; + }; +in + this diff --git a/Omni/Bild/Python.nix b/Omni/Bild/Python.nix index 035b11c..36abe25 100644 --- a/Omni/Bild/Python.nix +++ b/Omni/Bild/Python.nix @@ -1,13 +1,28 @@ _self: super: { python312 = super.python312.override { - packageOverrides = _pyself: pysuper: + packageOverrides = pyself: pysuper: with pysuper.pkgs.python312Packages; let dontCheck = p: p.overridePythonAttrs (_: {doCheck = false;}); in { interegular = callPackage ./Deps/interegular.nix {}; ipython = dontCheck pysuper.ipython; + llm = super.overrideSrc pysuper.llm super.sources.llm; + llm-ollama = pysuper.pkgs.python312.pkgs.callPackage ./Deps/llm-ollama.nix { + ollama = pyself.ollama; + }; + llm-sentence-transformers = callPackage ./Deps/llm-sentence-transformers.nix {}; ludic = callPackage ./Deps/ludic.nix {}; mypy = dontCheck pysuper.mypy; + ollama = pysuper.ollama.overridePythonAttrs (old: rec { + dependencies = old.dependencies ++ [pysuper.pydantic]; + version = super.sources.ollama-python.version; + src = super.sources.ollama-python; + postPatch = '' + substituteInPlace pyproject.toml \ + --replace-fail "0.0.0" "${version}" + ''; + }); + openai = callPackage ./Deps/openai-python.nix {}; outlines = callPackage ./Deps/outlines.nix {}; perscache = callPackage ./Deps/perscache.nix {}; tokenizers = dontCheck pysuper.tokenizers; diff --git a/Omni/Bild/Sources.json b/Omni/Bild/Sources.json index 5d3706d..02472c3 100644 --- a/Omni/Bild/Sources.json +++ b/Omni/Bild/Sources.json @@ -69,12 +69,12 @@ "homepage": "https://llm.datasette.io", "owner": "simonw", "repo": "llm", - "rev": "0.13.1", - "sha256": "0305xpmigk219i2n1slgpz3jwvpx5pdp5s8dkjz85w75xivakbin", + "rev": "41d64a8f1239322e12aa11c17450054f0c654ed7", + "sha256": "1vyg0wmcxv8910iz4cx9vjb3y4fq28423p62cgzr308ra8jii719", "type": "tarball", - "url": "https://github.com/simonw/llm/archive/0.13.1.tar.gz", + "url": "https://github.com/simonw/llm/archive/41d64a8f1239322e12aa11c17450054f0c654ed7.tar.gz", "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz", - "version": "0.13.1" + "version": "0.21" }, "niv": { "branch": "master", @@ -152,10 +152,10 @@ "homepage": "", "owner": "nixos", "repo": "nixpkgs", - "rev": "a5e6a9e979367ee14f65d9c38119c30272f8455f", - "sha256": "08yfk81kpsizdzlbi8whpaarb0w0rw9aynlrvhn5gr5dfpv9hbsf", + "rev": "ff0654c494b7484c4854ddabecdb91b0b7f7c4d0", + "sha256": "0iq3ygljsf28qzlawdxyipd467ha9chy75sj938wgvxc4qaaipk6", "type": "tarball", - "url": "https://github.com/nixos/nixpkgs/archive/a5e6a9e979367ee14f65d9c38119c30272f8455f.tar.gz", + "url": "https://github.com/nixos/nixpkgs/archive/ff0654c494b7484c4854ddabecdb91b0b7f7c4d0.tar.gz", "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" }, "nvidia-patch-nixos": { @@ -170,6 +170,29 @@ "url": "https://github.com/icewind1991/nvidia-patch-nixos/archive/bb8ac52eff4c4e8df0a18ab444263f2619d0d25a.tar.gz", "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" }, + "ollama-python": { + "branch": "main", + "description": "Ollama Python library", + "homepage": "https://ollama.com", + "owner": "ollama", + "repo": "ollama-python", + "rev": "ee349ecc6d05ea57c9e91bc9345e2db3bc79bb5b", + "sha256": "1dkrdkw7gkr9ilfb34qh9vwm0231csg7raln69p00p4mvx2w53gi", + "type": "tarball", + "url": "https://github.com/ollama/ollama-python/archive/refs/tags/v0.4.5.tar.gz", + "url_template": "https://github.com/<owner>/<repo>/archive/refs/tags/v<version>.tar.gz", + "version": "0.4.5" + }, + "openai-python": { + "branch": "main", + "description": "The official Python library for the OpenAI API", + "homepage": "https://pypi.org/project/openai/", + "owner": "openai", + "repo": "https://github.com/openai/openai-python", + "rev": "5e3e4d1b0f16ccc4469a90a5bff09cafe0de7a2e", + "type": "git", + "version": "1.56.1" + }, "outlines": { "branch": "main", "description": "Generative Model Programming", diff --git a/Omni/Log.py b/Omni/Log.py index 8d61139..7e3fdd3 100644 --- a/Omni/Log.py +++ b/Omni/Log.py @@ -32,5 +32,5 @@ def setup(level: int = logging.INFO) -> logging.Logger: def main() -> None: """Entrypoint to test that this kinda works.""" - setup() - logging.debug("i am doing testing") + logger = setup() + logger.debug("i am doing testing") diff --git a/Omni/Repl.py b/Omni/Repl.py index 8d191e2..d7d2fb4 100755 --- a/Omni/Repl.py +++ b/Omni/Repl.py @@ -20,7 +20,6 @@ additional files to load. import importlib import importlib.util import inspect -import logging import mypy.api import Omni.Log as Log import os @@ -34,6 +33,8 @@ import types import typing import unittest +LOG = Log.setup() + class ReplError(Exception): """Type for errors at the repl.""" @@ -48,7 +49,7 @@ def use(ns: str, path: str) -> None: Raises: ReplError: if module cannot be loaded """ - logging.info("loading %s from %s", ns, path) + LOG.info("loading %s from %s", ns, path) spec = importlib.util.spec_from_file_location(ns, path) if spec is None or spec.loader is None: msg = f"spec could not be loaded for {ns} at {path}" @@ -71,7 +72,7 @@ def typecheck(path: str) -> None: # this envvar is undocumented, but it works # https://github.com/python/mypy/issues/13815 os.environ["MYPY_FORCE_COLOR"] = "1" - logging.info("typechecking %s", path) + LOG.info("typechecking %s", path) stdout, stderr, _ = mypy.api.run([path]) sys.stdout.write(stdout) sys.stdout.flush() @@ -89,7 +90,7 @@ def edit_file(ns: str, path: str, editor: str) -> None: try: proc = subprocess.run([editor, path], check=False) except FileNotFoundError: - logging.exception("editor '%s' not found", editor) + LOG.exception("editor '%s' not found", editor) if proc.returncode == 0: use(ns, path) typecheck(path) |