fix(llm): normalize bare LM Studio / Ollama base URL to /v1 (#2922) - #2941
Merged
Conversation
LM Studio's server UI advertises its address as a bare host (http://localhost:1234), so users commonly set HINDSIGHT_API_LLM_BASE_URL to that. The OpenAI SDK then POSTs to <host>/chat/completions and LM Studio rejects it with 'Unexpected endpoint or method' — its OpenAI-compatible routes live under /v1. For lmstudio/ollama (whose OpenAI-compat surface is known to live under /v1) append /v1 when the base URL has no meaningful path. Explicit paths (reverse proxy mounts, already-correct /v1) are left untouched. Fixes #2922
nicoloboschi
added a commit
that referenced
this pull request
Jul 24, 2026
The three Oracle jobs run the Oracle 23ai `free` service image, which together with the Python ML deps (torch) exhausts the runner's ~14 GB root disk. Two symptoms, one cause: - uv fails to extract a wheel with "No space left on device (os error 28)" (fast ~2 min failure), and - a near-full disk starves I/O badly enough to trip the 30-minute job timeout. test-python-client-oracle and test-typescript-client-oracle have been red on every open PR (#2941, #2942, #2943) from this, independent of the code under test. Reclaim ~20 GB of preinstalled tooling (the same jlumbroso action the Docker build job already uses) before the Oracle setup step. docker-images stays false here: unlike the Docker build job, the Oracle service container is already running by the time steps execute, so pruning images could disrupt it. The savings come from the tool cache, Android SDK, .NET, Haskell, large apt packages and swap.
nicoloboschi
added a commit
that referenced
this pull request
Jul 24, 2026
…ain deadlock (#2948) * ci(oracle): free runner disk space before Oracle jobs The three Oracle jobs run the Oracle 23ai `free` service image, which together with the Python ML deps (torch) exhausts the runner's ~14 GB root disk. Two symptoms, one cause: - uv fails to extract a wheel with "No space left on device (os error 28)" (fast ~2 min failure), and - a near-full disk starves I/O badly enough to trip the 30-minute job timeout. test-python-client-oracle and test-typescript-client-oracle have been red on every open PR (#2941, #2942, #2943) from this, independent of the code under test. Reclaim ~20 GB of preinstalled tooling (the same jlumbroso action the Docker build job already uses) before the Oracle setup step. docker-images stays false here: unlike the Docker build job, the Oracle service container is already running by the time steps execute, so pruning images could disrupt it. The savings come from the tool cache, Android SDK, .NET, Haskell, large apt packages and swap. * ci(oracle): trim disk reclaim to the fast, high-yield options The first pass enabled every reclaim, which cost ~4 minutes of job time — counterproductive on jobs that are already fighting a 30-minute limit. android + dotnet + haskell + swap are a few rm -rf's worth ~16-21 GB, which is ample headroom for the Oracle image plus torch. Dropped: - large-packages: apt-get remove, costs minutes for little extra space; - tool-cache: deletes the preinstalled Python that actions/setup-python then re-downloads, making the job slower rather than faster. * fix(retain): flush entity stats after releasing the connection (Oracle hang) Retain hung forever on the Oracle backend: every retain test burned its 120s client timeout while the server sat idle, so test-python-client-oracle and test-typescript-client-oracle only ever reached ~5% of the suite before the 30-minute job limit. The server was not slow — it was deadlocked. flush_pending_stats() acquires its own connection, but it was being called while the enclosing acquire_with_retry(...) block still held one: async with acquire_with_retry(pool) as conn: # conn checked out async with conn.transaction(): # SAVEPOINT only ...write facts/entities... await entity_resolver.flush_pending_stats() # takes a 2nd connection oracledb does not autocommit and OracleConnection.transaction() is only a SAVEPOINT, so the write is committed by OracleBackend.acquire() when its block exits. Connection #2's `UPDATE entities ...` therefore waits on row locks held by the still-open connection #1, which cannot commit until the call returns — a circular wait. Oracle never reports ORA-00060 because session #1 is blocked in Python, not on the database, so it hangs indefinitely instead of erroring. Move the flush after the acquire block in all three call sites (streaming retain, delta retain, transfer importer), which is what its own docstring already required ("must be called AFTER the retain transaction commits") and which PostgreSQL satisfied only by accident via asyncpg autocommit. Guarded with an AST lint test rather than a behavioural one: the deadlock cannot be reproduced against PostgreSQL, which is what the suite runs on. * test(repair): retry the concurrent index drop on deadlock test_dry_run_creates_nothing still flaked in test-api shard 3. CONCURRENTLY avoids ACCESS EXCLUSIVE but still takes ShareUpdateExclusive, which conflicts with the ShareLock a fresh bank's plain CREATE INDEX holds — and that one cannot be made concurrent, since it runs inside the bank-create transaction. So _drop_bank_indexes can still be picked as the deadlock victim while another xdist worker seeds a bank: Process A waits for ShareUpdateExclusiveLock on memory_units; blocked by B. Process B waits for ShareLock on virtual transaction; blocked by A. The bank-create side already retries (#2943); give the drop the same treatment. The drop is idempotent, so retrying is safe.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Hindsight fails against LM Studio when the base URL is set to the bare host that LM Studio's server UI advertises (
http://localhost:1234, without/v1). The OpenAI SDK appends/chat/completionsto the base URL, so it POSTs tohttp://localhost:1234/chat/completions, and LM Studio rejects it:LM Studio's OpenAI-compatible routes live under
/v1(/v1/chat/completions) — the bare host has no such route. Its newer native REST endpoints (/api/v1/chat) are not OpenAI-compatible, so the OpenAI SDK can't target them anyway;/v1/chat/completionsremains the correct endpoint and it still works.Fixes #2922.
Fix
For
lmstudioandollama— the two local providers whose OpenAI-compatible surface is known to live under/v1— normalize a base URL that has no meaningful path by appending/v1. Base URLs with an explicit path (an already-correct/v1, or a reverse-proxy mount) are left untouched, and cloud/proxy providers are not affected.http://localhost:1234http://localhost:1234/v1http://localhost:1234/http://localhost:1234/v1http://localhost:1234/v1http://proxy.internal/lmstudioVerification
test_local_provider_base_url.pycover the normalization matrix and assert the constructed OpenAI client targets/v1/chat/completions.http://localhost:1234(the exact config from the issue) now succeeds instead of returningUnexpected endpoint or method.