Skip to content

OCI auto-detection: notebook docs over-promise vs. constructor; harness detector is naive #276

@fede-kamel

Description

@fede-kamel

Summary

22 notebook intros under docs/notebooks/ claim that "OCI Generative AI is the default; auto-detected from ~/.oci/config". In practice this is true only for the notebook harness (examples/config.py), and even there the detection is little more than a Path.exists() check that hard-codes the profile name DEFAULT. Users who follow the docs but construct OCIChatCompletionsModel(...) directly — i.e., the pattern shown in docs/how-to/oci-models.md — get Got profile=None, auth_type=None and are reasonably confused about why the advertised auto-detection didn't kick in.

What users actually hit

A reader who skims a notebook page sees "auto-detected from ~/.oci/config" and writes the obvious thing:

from locus.agent import Agent
from locus.models.providers.oci.openai_compat import OCIChatCompletionsModel

model = OCIChatCompletionsModel(model="<model>")   # no profile=
agent = Agent(model=model)

This raises immediately at src/locus/models/providers/oci/openai_compat.py:304-313:

OCIChatCompletionsModel: specify exactly one auth mode...
Got profile=None, auth_type=None.

The fix is one kwarg (profile="DEFAULT"), but the docs gave the wrong mental model and the error doesn't point back at the doc claim.

Root cause

Two separate weaknesses, both worth fixing:

1. Notebook prose oversells the auto-detection

22 notebook docs (notebook_13, 35, 36, 46, 49, 50, 51, 55, 56, 57, 58, 62, 64, …) open with:

"OCI Generative AI is the default; auto-detected from ~/.oci/config"

The phrasing reads as a property of the SDK. It is in fact a property of examples/config.py's get_model() helper. Readers who copy OCIChatCompletionsModel(...) directly (as shown in docs/how-to/oci-models.md) get no such auto-detection.

2. The harness "auto-detection" is a file-exists check

From examples/config.py:221-235:

def _auto_detect_provider() -> str:
    if os.environ.get("LOCUS_OCI_AUTH_TYPE") in ("instance_principal", "resource_principal"):
        return "oci"
    if os.environ.get("OCI_AUTH_TYPE") in ("instance_principal", "resource_principal"):
        return "oci"
    if (Path.home() / ".oci" / "config").exists():
        return "oci"
    return "mock"

It never parses the config. The profile name then comes from LOCUS_OCI_PROFILEOCI_PROFILE → hard-coded "DEFAULT". Consequences:

  • Users with multiple profiles and no [DEFAULT] section get an obscure OCI SDK stack trace, because the harness happily picks provider="oci" and then asks the SDK to load a profile that doesn't exist.
  • Users with a [DEFAULT] that points at the wrong tenancy/region silently use the wrong account.
  • There is no friendly error pointing the user at OCI_PROFILE.

Proposed solution

1. Fix the harness detector (examples/config.py). Replace the file-exists check with real parsing:

  • If ~/.oci/config exists, parse it (via oci.config.from_file or configparser).
  • [DEFAULT] present → use it.
  • Exactly one non-default profile → use it.
  • Multiple profiles, no [DEFAULT], no OCI_PROFILE env var → raise a clear error:

    "Found N profiles in ~/.oci/config (X, Y, Z) but no [DEFAULT] section and OCI_PROFILE is unset. Set export OCI_PROFILE=<name> to pick one."

  • Workload-identity env vars still short-circuit to oci as today.

2. Tighten the 22 notebook intros so the prose matches reality. Replace:

"OCI Generative AI is the default; auto-detected from ~/.oci/config"

with:

"OCI GenAI is the default — the notebook harness auto-selects it when ~/.oci/config exists. Set OCI_PROFILE to pick a non-default profile."

One mechanical pass across the 22 files.

Explicitly not proposing: changing OCIChatCompletionsModel.__init__ to default profile="DEFAULT" when both auth args are None. The constructor's strict contract is a feature — silently defaulting would mask "user has no OCI setup at all" (constructor succeeds, first API call fails with a worse error than today's clear validation). The notebook prose is the bug, not the constructor.

Optional follow-up: expose a public helper locus.models.providers.oci.autodetect_kwargs() returning a kwargs dict (or raising the same clear error). Lets power users do OCIChatCompletionsModel(model=..., **autodetect_kwargs()) without weakening the constructor's contract. Worth doing only if anyone asks for it after #1 and #2 ship.

Acceptance criteria

  • examples/config.py::_auto_detect_provider parses ~/.oci/config and returns a usable provider+profile pair, or raises a clear error.
  • A user with ~/.oci/config containing only non-default profiles gets a friendly error (not an OCI SDK trace) the first time they run a notebook without setting OCI_PROFILE.
  • The 22 notebook intros are updated so the auto-detection claim is scoped to the harness.
  • docs/how-to/oci-models.md is unchanged (it already shows profile="DEFAULT" explicitly and is correct).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions