Skip to content

feat(llm): add OCI Generative AI provider - #5186

Closed
Xiaowen-Yang wants to merge 2 commits into
OpenHands:mainfrom
Xiaowen-Yang:feat/17512-oci-generative-ai-provider
Closed

Xiaowen-Yang wants to merge 2 commits into
OpenHands:mainfrom
Xiaowen-Yang:feat/17512-oci-generative-ai-provider

Conversation

@Xiaowen-Yang

@Xiaowen-Yang Xiaowen-Yang commented Sep 19, 2026 •

Copy link
Copy Markdown

HUMAN:
I reviewed the OCI Generative AI provider implementation and the automated test results. I did not run a live OCI request because I do not have the required OCI credentials.


AGENT:

Why

OCI Generative AI provides an OpenAI-compatible Responses API, but OpenHands
currently has no first-class configuration path for its regional endpoint,
Generative AI project OCID, or API-key authentication.

Users must manually construct the endpoint, add the openai-project header,
and configure Responses API capabilities. This is error-prone and makes OCI
deployments difficult to configure reliably.

This PR adds the SDK and Agent Server support needed for a guided OCI Generative
AI profile. The dependent Agent Canvas UI is implemented in
OpenHands/OpenHands#17543, and the user-facing setup documentation is provided
in OpenHands/docs#813.

Summary

  • Add oci_genai/<model-id> as a first-class LLM provider route with validated
    OCI region and Generative AI project OCID fields.
  • Derive the regional /openai/v1 endpoint, select the Responses API, and add
    the required openai-project header automatically.
  • Add provider discovery, request-construction, normal-response, tool-call,
    validation, serialization, and encrypted-secret tests.

Issue Number

Fixes: #5188
Related to OpenHands/OpenHands#17512.

How to Test

Automated Verification Performed

Set up the development environment:

make build

Result: completed successfully.

Run the targeted OCI and provider-discovery tests:

uv run pytest -q \
  tests/sdk/llm/test_oci_genai_provider.py \
  tests/agent_server/test_llm_router.py

Result:

28 passed, 3 warnings

Run the broader LLM profile and Agent Server regression tests:

uv run pytest -q --disable-warnings \
  tests/sdk/llm/test_llm.py \
  tests/sdk/llm/test_llm_profile_store.py \
  tests/agent_server/test_profiles_router.py

Result:

246 passed, 4 warnings

Run the repository checks on all changed files:

uv run pre-commit run --files \
  openhands-sdk/openhands/sdk/llm/llm.py \
  openhands-sdk/openhands/sdk/llm/utils/oci_genai_provider.py \
  openhands-agent-server/openhands/agent_server/llm_router.py \
  tests/sdk/llm/test_oci_genai_provider.py \
  tests/agent_server/test_llm_router.py

Result:

Ruff format........................................................Passed
Ruff lint..........................................................Passed
PEP8 style check (pycodestyle).....................................Passed
Type check with pyright............................................Passed
Forbid dynamic attribute access in SDK.............................Passed
Check import dependency rules......................................Passed
Check Tool subclass registration...................................Passed

Validate the generated Agent Server OpenAPI schema:

make test-server-schema

Result:

Agent Server OpenAPI type-quality check passed.
openapi.json is valid

Live OCI Verification

A credentialed live request was not run because no OCI Generative AI API key,
project OCID, and region-enabled model were available in the development
environment.

The automated tests exercise both normal Responses API output and function-tool
output through mocked provider responses, but they do not make a billable OCI
request. Credentialed validation by a maintainer with OCI access would be
appreciated. The live verification procedure is:

  1. Create or select an OCI Generative AI project.
  2. Create a Generative AI API key.
  3. Select a model available in the same OCI region.
  4. Configure an LLM with:
    • model="oci_genai/<exact-model-id>"
    • oci_region="<region>"
    • oci_project_id="<project-ocid>"
    • the OCI Generative AI API key
  5. Send a normal Responses API request and confirm that text is returned.
  6. Start an agent conversation that requests a terminal tool call.
  7. Confirm that the model invokes the tool and continues after consuming its
    result.

OpenHands/docs#813 includes the same credential-gated live test plan for users.

Video/Screenshots

Not applicable to this SDK and Agent Server PR because it does not change a GUI.

OpenHands/OpenHands#17543 contains the user-facing provider selector, OCI
configuration fields, and a screenshot of the rendered form. CLI test results
and reproduction steps are included above.

Design Doc

Not included. The implementation is limited to provider-specific configuration
normalization and transport routing:

oci_genai/<model-id>
        |
        v
openai/<model-id> through LiteLLM
        |
        v
https://inference.generativeai.<region>.oci.oraclecloud.com/openai/v1
        |
        +-- openai-project: <Generative AI project OCID>

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • oci_genai/ is intentionally separate from LiteLLM's native oci/ provider.
    The native route targets OCI IAM-style authentication, while this initial
    integration uses OCI Generative AI API keys and the OpenAI-compatible
    Responses API.
  • Model IDs are not hardcoded because OCI model availability differs by region.
  • OCI IAM authentication through user, session, instance, or resource
    principals is outside the scope of this PR and can be added in a follow-up.
  • The provider-required openai-project header takes precedence over conflicting
    caller-supplied headers.
  • Agent Canvas UI: feat(llm): add OCI Generative AI setup OpenHands#17543.
  • User-facing setup documentation: docs(llm): document OCI Generative AI setup docs#813.

Jev-Fast-Audit

⚡ Jev fast audit · estimates · 0.52s · commit 1f8bab5
Strongest signal: No primary concern selected.
Evidence: No primary concern to locate.
Coverage: complete supplied coverage; 14/14 hunks, 5/5 files.

All estimates and evidence
Estimate Likelihood / value Direct evidence
SQL injection 3.0% No direct hunk selected
Command injection 5.0% No direct hunk selected
Weakened authentication 6.0% No direct hunk selected
Weakened authorization 9.0% No direct hunk selected
Contract regression 17.0% No direct hunk selected
Data loss 3.0% No direct hunk selected
Sensitive data disclosure 10.0% No direct hunk selected
Unexpected data transfer 4.0% No direct hunk selected
Credential misuse 10.0% No direct hunk selected
Untrusted instruction authority 2.0% No direct hunk selected
Package source redirection 3.0% No direct hunk selected
Unverified remote execution 2.0% No direct hunk selected
Privileged environment access 3.0% No direct hunk selected
Security assessment bypass 6.0% No direct hunk selected
Prohibited workload 2.0% No direct hunk selected
Primary concern None selected; confidence 83.0% No primary concern to locate

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🚦 CI is currently failing on this PR's latest commit.

Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request @all-hands-bot as a reviewer to have it reviewed regardless of CI status.)

This is an automated check - no AI was used to generate this comment.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Scope

This change belongs in this repository: it adds an LLM provider route plus the Agent Server provider-discovery entry, which is exactly what software-agent-sdk owns. Linked issue #5188 is open with enhancement, ready-for-dev, priority:normal, and the acceptance criteria match the diff. No repository move or product decision is needed.

What I verified

  • Head is still 1f8bab5 (matches the requested SHA).
  • Ran the focused tests in the workspace on this head: uv run pytest -q tests/sdk/llm/test_oci_genai_provider.py tests/agent_server/test_llm_router.py -> 28 passed. Broader regression set (test_llm.py, test_llm_profile_store.py, test_profiles_router.py, test_settings.py) -> 385 passed.
  • Endpoint derivation, openai-project header precedence over caller-supplied headers, region/OCID validation, and to_persisted/from_persisted round-trip all behave as the tests claim.

Findings

1. model_copy(update={"model": ...}) bypasses the OCI normalization and leaves derived routing stale (material).

The OCI routing (base_url = None, api_mode = "responses") is applied only in the _coerce_inputs before-validator. model_copy does not re-run validators, and the new code only rebuilds LiteLLM metadata, so a copied LLM keeps the source's api_mode/base_url while _litellm_call_kwargs/_provider_headers already switch to the OCI transport. Changing the model via model_copy is an established pattern in this repo (existing test test_llm_model_copy_refreshes_provider_for_model_update), so this path is reachable:

  • LLM(model="openai/gpt-5-mini").model_copy(update={"model": "oci_genai/xai.grok-4.6"}) raises a bare AssertionError from the new assert self.oci_region is not None in _litellm_call_kwargs.
  • With oci_region/oci_project_id supplied, the copy builds the correct api_base and header but keeps api_mode = "auto", so uses_responses_api() returns False and the call is dispatched through the chat-completions path instead of the Responses API. A pre-existing non-None base_url would also be retained and route the request to the wrong host.
  • The inverse leaks too: oci_llm.model_copy(update={"model": "anthropic/claude-sonnet-4-5"}) keeps api_mode = "responses", pinning a non-OCI model to the Responses API.

Because OCI's derived api_mode/base_url are stashed on the model rather than recomputed from model + oci_region, model_copy needs to re-derive them (or _litellm_call_kwargs/_provider_headers should not assume the copy went through _coerce_inputs).

2. CI is not green for this head.

The required workflows on 1f8bab5 (Run tests, Agent Server, Pre-commit checks, TypeScript client CI, etc.) are all action_required — they have not completed successfully for this commit — and the bot posted a CI-failing notice on this commit. The only currently-green checks are PR Description Check and PR Artifacts. Local focused runs pass, but there is no completed CI signal on the head, so this needs a maintainer look regardless of finding 1.

Note

Live credentialed OCI verification is explicitly not covered by this PR (author states no OCI credentials were available); the tests exercise only mocked responses. That is reasonable for a new provider, but the OCI-specific routing is exactly where finding 1 lives, so the model_copy path deserves a test.

🔄 CHANGES REQUESTED

Comment thread openhands-sdk/openhands/sdk/llm/llm.py Outdated
Co-authored-by: openhands <openhands@all-hands.dev>

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Scope

In-repo. This adds an LLM provider route plus the Agent Server provider-discovery entry — exactly the surface software-agent-sdk owns (SDK/Agent Server → OpenAPI → clients → Agent Canvas). Linked issue #5188 is open with enhancement, ready-for-dev, priority:low, and its acceptance criteria match the diff. No repository move or product/architecture decision is needed.

Prior finding is resolved

The earlier model_copy(update={"model": ...}) gap is fixed on this head. I re-verified each case directly against the workspace:

  • non-OCI → oci_genai with region/project: api_mode becomes responses, stale base_url is dropped (None), uses_responses_api() is True, and _litellm_call_kwargs() returns the regional OCI api_base.
  • non-OCI → oci_genai without oci_region: raises the intended ValueError("oci_region is required for OCI Generative AI") instead of the previous bare AssertionError.
  • oci_genai → non-OCI: api_mode resets to auto and uses_responses_api() is False (previously it stayed pinned to the Responses API).
  • OCI → different region / project: re-derived api_base and openai-project header are correct.

Verification performed

  • Head still e2bb49d9 (matches the requested SHA); PR open, not draft, base main.
  • uv run pytest -q tests/sdk/llm/test_oci_genai_provider.py tests/agent_server/test_llm_router.py → 31 passed.
  • uv run pytest -q tests/sdk/llm/test_llm.py tests/sdk/llm/test_llm_profile_store.py tests/agent_server/test_profiles_router.py tests/sdk/test_settings.py tests/sdk/llm/test_model_list.py → 395 passed.
  • Reviewed the endpoint derivation, openai-project header precedence (provider header wins over caller-supplied), region/OCID validation (dot-free region regex prevents host injection), and the to_persisted/from_persisted round-trip. All behave as the tests claim.

CI status (informational, not blocking)

All twelve pull_request workflows on this head are action_required with zero jobs — they have not executed yet, because this is a fork PR awaiting first-time-contributor workflow approval. The only completed checks are PR Description Check and PR Artifacts (both success). Nothing here is a failing run; it just means a maintainer needs to approve the workflow runs before the standard suite reports. The PR has not modified any release/version fields and adds no dependencies, so the guide's non-approve conditions (version bump, too-new dependency) do not apply, and this is not an eval-risk change (new provider only; existing models and the agent loop are unaffected).

Live credentialed OCI verification is explicitly out of scope per the description (no OCI credentials available); tests exercise mocked Responses output and function-tool output. A maintainer with OCI access should still run the listed live procedure before merge.

✅ APPROVED

@neubig

neubig commented Sep 25, 2026

Copy link
Copy Markdown
Member

Thank you for the substantial OCI Generative AI provider implementation. After review, we are closing this PR because adding new AI providers is generally out of scope unless the provider is broadly popular and commonly used by OpenHands users. We are not recommending OCI as a built-in provider at this time. This decision is about project scope, not the quality of the implementation. An AI agent (OpenHands) posted this comment on behalf of the user.

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.

[Feature]: Add OCI Generative AI provider support

3 participants