blog

uv for LLM Experiments

Using uv instead of Anaconda environment.yml to set up a virtual environment for LLM experiments.

pyproject.toml for an LLM project

I’m a uv convert. It’s a much faster way to set up isolated virtual environments for Python than other methods that I’ve used, including Anaconda and the venv + pip combination.

Anaconda Baseline

For my current course on AI Engineering the code expects the environment to be fully managed using Anaconda. I have zero problem with that. It’s all nicely packaged up in a single file:

environment.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
name: rag-chatbot
channels:
  - conda-forge
dependencies:
  - python=3.11
  - pip
  - pip:
      - langchain==0.3.25
      - langchain-community==0.3.24
      - sentence-transformers==4.1.0
      - streamlit==1.45.1
      - openai==1.79.0
      - faiss-cpu==1.11.0
      - unstructured==0.17.2

It’s all very clear what this project relies on. But I don’t use Anaconda: I use asdf to manage languages and runtimes, including Python. And I’ve started using uv for my Python projects.

Using the right Python

First I use asdf to install the right Python version. I decided to use a more recent version of Python that the Anaconda baseline.

1
2
3
4
asdf install python 3.14.0
...
cd ~/projects/ai-eng-projects/project_2
asdf set python 3.14.0

The last line sets the Python version for the current directory by creating a .tool-versions file:

1
python 3.14.0

If you want to use uv to manage your Pythons, you can totally do that by following their detailed instructions. I use asdf for all of my languages and runtimes, so I prefer to use it for setting project-local versions. Here’s that Python version for my project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ asdf list
flutter
 *3.35.1-stable
golang
 *1.24.4
nodejs
 *24.2.0
python
  3.13.4t
  3.13.5
 *3.14.0
  anaconda3-2025.06-1
rust
  1.89.0
 *1.90.0

Adding uv Support

I working in an existing git repository that I don’t want to litter with all kinds of uv-isms, like a new README.md or a dedicated directory. I just want to use uv alongside my asdf-managed Python versions.

Basic Configuration

First, let’s use uv in the most minimal way possible:

1
2
$ uv init . --bare
Initialized project `project-2` at `/Users/bitsbyme/projects/ai-eng-projects/project_2`

This does exactly one thing: it creates a basic project file in the current directory (.) rather the the typical behaviour of creating a new directory:

toml-bare.toml

1
2
3
4
5
[project]
name = "project-2"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = []

Add Dependencies

This is where I think uv shines: it’s incredibly fast to detect, resolve, and install dependencies. pip just takes an age in the build process once it’s downloaded whereas uv, as far as a understand, has prebuilt binaries for the dependencies.

First attempt:

1
2
3
4
5
6
7
8
$ uv add "langchain==0.3.25" "langchain-community==0.3.24" "sentence-transformers==4.1.0" "streamlist==1.45.1" "openai==1.79.0" "faiss-cpu==1.11.0" "unstructured==0.17.2"
Using CPython 3.13.5 interpreter at: /Users/bitsbyme/.asdf/installs/python/anaconda3-2025.06-1/bin/python3
Creating virtual environment at: .venv
  × No solution found when resolving dependencies:
  ╰─▶ Because streamlist was not found in the package registry and your project depends on
      streamlist==1.45.1, we can conclude that your project's requirements are unsatisfiable.
  help: If you want to add the package regardless of the failed resolution, provide the
        `--frozen` flag to skip locking and syncing.

Oh jeez. I misspelled streamlit! But hey, and least uv created a virtual environment for me!

Let’s try again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ uv add "langchain==0.3.25" "langchain-community==0.3.24" \
  "sentence-transformers==4.1.0" "streamlit==1.45.1" "openai==1.79.0" \
  "faiss-cpu==1.11.0" "unstructured==0.17.2"
Using CPython 3.13.5 interpreter at: /Users/bitsbyme/.asdf/installs/python/anaconda3-2025.06-1/bin/python3
Creating virtual environment at: .venv
Resolved 138 packages in 3.63s
      Built langdetect==1.0.9
Prepared 64 packages in 43.27s
Installed 119 packages in 778ms
 + aiofiles==25.1.0
 + aiohappyeyeballs==2.6.1
 + aiohttp==3.13.0
 ...
 ... lots and lots of dependencies
 ...
 + webencodings==0.5.1
 + wrapt==1.17.3
 + yarl==1.22.0
 + zstandard==0.23.0

So this whole thing just took less that one minute to fetch the same things that Anaconda expects, including a build of one library that hadn’t been cached in advance. If you want to read all the gory details, run uv tree and get a sense for the sheer number of dependencies that were installed. If you really want to know what the project depends on, uv did also create a uv.lock file. It’s dense!

Using uv

If I were to check in my changes to the git repo, they’d be quite modest:

1
2
3
4
5
6
7
8
9
$ git status
On branch uv
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        project_2/.tool-versions
        project_2/pyproject.toml
        project_2/uv.lock

nothing added to commit but untracked files present (use "git add" to track)

Three files that will let a subsequent contributor to do the following.

  1. Install uv.
  2. Set up the correct Python and virtual environment. That could be with asdf, uv, or whatever makes you happy.
  3. Run uv sync and get blazing fast results.

If you discover that there’s a missing dependency, uv add ... comes to the rescue, and you’d commit the updated pyproject.toml and uv.lock to your repo.

Conclusion

There’s nothing wrong with Anaconda, but it’s not the only way to manage Python projects. uv is a lightweight alternative that can be used to manage Python projects their dependencies.


See Also

View page source