summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-02-04 21:18:03 -0500
committerBen Sima <ben@bsima.me>2025-02-04 21:18:03 -0500
commit9f5b334eb6d0f64460f14d76255b096777a46332 (patch)
treedb357ca2a933456e1f35c17a1c85000fb5a64b9d
parent86ea51353c223cdc82cb8ebd013d58d70a7e646a (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-xBiz/Que/Client.py34
-rwxr-xr-xBiz/Storybook.py22
-rw-r--r--Omni/Bild/Deps.nix4
-rw-r--r--Omni/Bild/Deps/Python.nix2
-rw-r--r--Omni/Bild/Deps/llm-ollama.nix57
-rw-r--r--Omni/Bild/Deps/openai-python.nix99
-rw-r--r--Omni/Bild/Nixpkgs.nix52
-rw-r--r--Omni/Bild/Python.nix17
-rw-r--r--Omni/Bild/Sources.json37
-rw-r--r--Omni/Log.py4
-rwxr-xr-xOmni/Repl.py9
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)