Skip to content

feat: add optional llm extra with OpenRouter client - #25

Open
m-messer wants to merge 3 commits into
cli-groupfrom
llm-client
Open

m-messer wants to merge 3 commits into
cli-groupfrom
llm-client

Conversation

@m-messer

@m-messer m-messer commented Sep 1, 2026

Copy link
Copy Markdown
Member

New in2lambda.llm package behind a Poetry llm extra (pip install 'in2lambda[llm]'openai, python-dotenv, pydantic):

  • client.get_client() returns an openai.OpenAI pointed at https://openrouter.ai/api/v1, keyed by $OPENROUTER_API_KEY (loaded from a .env file if present). Clear RuntimeError when the extra is missing or the key is unset.
  • client.resolve_model() picks the model slug: --model value, else $IN2LAMBDA_MODEL, else DEFAULT_MODEL (openai/gpt-4o-mini).

The package imports cleanly without the extra (openai/dotenv guarded), so --doctest-modules still works on a bare install. CI now installs --all-extras. Tests are fully offline.

openai floor is ^1.55 — older releases resolve against a too-new httpx and break with a proxies TypeError.


Stack: cli-group ← llm-client ← mathpix ← wizard-command
Base: cli-group#24.

🤖 Generated with Claude Code

New in2lambda.llm package behind a Poetry `llm` extra
(pip install 'in2lambda[llm]' -> openai, python-dotenv, pydantic):

- client.get_client() returns an openai.OpenAI pointed at
  https://openrouter.ai/api/v1, keyed by $OPENROUTER_API_KEY (loaded from a
  .env file if present). Raises a clear RuntimeError when the extra is
  missing or the key is unset.
- client.resolve_model() picks the model slug: --model value, else
  $IN2LAMBDA_MODEL, else DEFAULT_MODEL (openai/gpt-4o-mini).

The package imports cleanly without the extra (openai/dotenv guarded), so
--doctest-modules still works on a bare install. CI now installs
--all-extras so the llm code is covered. Tests are fully offline.

openai floor is ^1.55 - older releases resolve against a too-new httpx and
break with a `proxies` TypeError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VXb8aZqgFBjoeuuddjW6r
@peterbjohnson

Copy link
Copy Markdown
Member

One real bug here, in .env handling.

get_client() calls load_dotenv() from inside the library (client.py:66). python-dotenv looks for the file by walking up from the calling module's directory, so once in2lambda is installed normally, it searches inside site-packages and never sees the user's .env. It only appears to work in a development checkout, where the package happens to sit under the project folder.

Minimal reproduction, with a library in one folder and a project in another:

project/.env            -> DEMO_KEY=from_project_dotenv
project/run_app.py      -> from libpkg import get_key; print(get_key())
site_packages/libpkg/   -> calls load_dotenv() then os.getenv("DEMO_KEY")

$ cd project && PYTHONPATH=../site_packages python3 run_app.py
key seen by library: None

find_dotenv(usecwd=True) finds it; the default doesn't. (It's easy to convince yourself otherwise from a REPL or python -c, where python-dotenv falls back to the working directory.)

This matters because the wizard docs in the next PR tell people a .env in the working directory is picked up automatically, which is exactly the case that fails.

Suggestions: load the .env once in the CLI entry point with usecwd=True, or drop python-dotenv and let click read the key via envvar=. Loading it from library code also changes the whole process's environment, which is a bit surprising for anyone importing in2lambda as a library.

Smaller points: resolve_model() duplicates what click's envvar=/default= already do on the --model option, and openai = "^1.55" caps us below openai 2.x, which will bite anyone combining in2lambda with a newer openai.

m-messer and others added 2 commits September 16, 2026 09:06
get_client() called plain load_dotenv(), whose default search walks up
from the calling module's file location. Once in2lambda is installed
normally that starts inside site-packages and never reaches the
user's project .env; it only appeared to work from a development
checkout. Use find_dotenv(usecwd=True) so the search starts from the
process's working directory instead.

Also relax the openai constraint from ^1.55 (which caps at <2.0) to
>=1.55,<3 so it doesn't conflict with openai 2.x.

Addresses review feedback on PR #25.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@m-messer

Copy link
Copy Markdown
Member Author

Thanks for the detailed repro — pushed a fix in c96e9ea:

  • .env bug: get_client() now calls load_dotenv(find_dotenv(usecwd=True)) instead of plain load_dotenv(), so the search starts from the process's working directory rather than the calling module's file location. Verified against your exact repro shape (project dir with .env, package invoked from elsewhere) — it now picks up the key correctly.
  • openai constraint: relaxed from ^1.55 to >=1.55,<3 so it no longer caps below 2.x, while keeping the 1.55 floor (still needed for the httpx proxies issue on older releases).
  • resolve_model() duplication: left as-is for now — there's no CLI command in this PR that would make it redundant (the wizard-command PR later in the stack is what adds --model). Will reconcile with click's envvar=/default= there once that option actually exists.

@m-messer
m-messer added this pull request to stack #48 September 16, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants